rocksndiamonds-3.1.2
[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 #if 1
942     printf("subdir == '%s' ['%s', '%s'] [%d])\n",
943            node->subdir, node->fullpath, node->basepath, node->in_user_dir);
944 #else
945     printf("subdir == '%s' (%s) [%s] (%d)\n",
946            node->subdir, node->name, node->identifier, node->sort_priority);
947 #endif
948
949     if (node->node_group != NULL)
950       dumpTreeInfo(node->node_group, depth + 1);
951
952     node = node->next;
953   }
954 }
955
956 void sortTreeInfo(TreeInfo **node_first,
957                   int (*compare_function)(const void *, const void *))
958 {
959   int num_nodes = numTreeInfo(*node_first);
960   TreeInfo **sort_array;
961   TreeInfo *node = *node_first;
962   int i = 0;
963
964   if (num_nodes == 0)
965     return;
966
967   /* allocate array for sorting structure pointers */
968   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
969
970   /* writing structure pointers to sorting array */
971   while (i < num_nodes && node)         /* double boundary check... */
972   {
973     sort_array[i] = node;
974
975     i++;
976     node = node->next;
977   }
978
979   /* sorting the structure pointers in the sorting array */
980   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
981         compare_function);
982
983   /* update the linkage of list elements with the sorted node array */
984   for (i = 0; i < num_nodes - 1; i++)
985     sort_array[i]->next = sort_array[i + 1];
986   sort_array[num_nodes - 1]->next = NULL;
987
988   /* update the linkage of the main list anchor pointer */
989   *node_first = sort_array[0];
990
991   free(sort_array);
992
993   /* now recursively sort the level group structures */
994   node = *node_first;
995   while (node)
996   {
997     if (node->node_group != NULL)
998       sortTreeInfo(&node->node_group, compare_function);
999
1000     node = node->next;
1001   }
1002 }
1003
1004
1005 /* ========================================================================= */
1006 /* some stuff from "files.c"                                                 */
1007 /* ========================================================================= */
1008
1009 #if defined(PLATFORM_WIN32)
1010 #ifndef S_IRGRP
1011 #define S_IRGRP S_IRUSR
1012 #endif
1013 #ifndef S_IROTH
1014 #define S_IROTH S_IRUSR
1015 #endif
1016 #ifndef S_IWGRP
1017 #define S_IWGRP S_IWUSR
1018 #endif
1019 #ifndef S_IWOTH
1020 #define S_IWOTH S_IWUSR
1021 #endif
1022 #ifndef S_IXGRP
1023 #define S_IXGRP S_IXUSR
1024 #endif
1025 #ifndef S_IXOTH
1026 #define S_IXOTH S_IXUSR
1027 #endif
1028 #ifndef S_IRWXG
1029 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1030 #endif
1031 #ifndef S_ISGID
1032 #define S_ISGID 0
1033 #endif
1034 #endif  /* PLATFORM_WIN32 */
1035
1036 /* file permissions for newly written files */
1037 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1038 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1039 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1040
1041 #define MODE_W_PRIVATE          (S_IWUSR)
1042 #define MODE_W_PUBLIC           (S_IWUSR | S_IWGRP)
1043 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1044
1045 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1046 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1047
1048 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1049 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC)
1050
1051 char *getUserDataDir(void)
1052 {
1053   static char *userdata_dir = NULL;
1054
1055   if (userdata_dir == NULL)
1056     userdata_dir = getPath2(getHomeDir(), program.userdata_directory);
1057
1058   return userdata_dir;
1059 }
1060
1061 char *getCommonDataDir(void)
1062 {
1063   static char *common_data_dir = NULL;
1064
1065 #if defined(PLATFORM_WIN32)
1066   if (common_data_dir == NULL)
1067   {
1068     char *dir = checked_malloc(MAX_PATH + 1);
1069
1070     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1071         && strcmp(dir, "") != 0)        /* empty for Windows 95/98 */
1072       common_data_dir = getPath2(dir, program.userdata_directory);
1073     else
1074       common_data_dir = options.rw_base_directory;
1075   }
1076 #else
1077   if (common_data_dir == NULL)
1078     common_data_dir = options.rw_base_directory;
1079 #endif
1080
1081   return common_data_dir;
1082 }
1083
1084 char *getSetupDir()
1085 {
1086   return getUserDataDir();
1087 }
1088
1089 static mode_t posix_umask(mode_t mask)
1090 {
1091 #if defined(PLATFORM_UNIX)
1092   return umask(mask);
1093 #else
1094   return 0;
1095 #endif
1096 }
1097
1098 static int posix_mkdir(const char *pathname, mode_t mode)
1099 {
1100 #if defined(PLATFORM_WIN32)
1101   return mkdir(pathname);
1102 #else
1103   return mkdir(pathname, mode);
1104 #endif
1105 }
1106
1107 void createDirectory(char *dir, char *text, int permission_class)
1108 {
1109   /* leave "other" permissions in umask untouched, but ensure group parts
1110      of USERDATA_DIR_MODE are not masked */
1111   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1112                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1113   mode_t normal_umask = posix_umask(0);
1114   mode_t group_umask = ~(dir_mode & S_IRWXG);
1115   posix_umask(normal_umask & group_umask);
1116
1117   if (!fileExists(dir))
1118     if (posix_mkdir(dir, dir_mode) != 0)
1119       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1120
1121   posix_umask(normal_umask);            /* reset normal umask */
1122 }
1123
1124 void InitUserDataDirectory()
1125 {
1126   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
1127 }
1128
1129 void SetFilePermissions(char *filename, int permission_class)
1130 {
1131   chmod(filename, (permission_class == PERMS_PRIVATE ?
1132                    FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1133 }
1134
1135 char *getCookie(char *file_type)
1136 {
1137   static char cookie[MAX_COOKIE_LEN + 1];
1138
1139   if (strlen(program.cookie_prefix) + 1 +
1140       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1141     return "[COOKIE ERROR]";    /* should never happen */
1142
1143   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1144           program.cookie_prefix, file_type,
1145           program.version_major, program.version_minor);
1146
1147   return cookie;
1148 }
1149
1150 int getFileVersionFromCookieString(const char *cookie)
1151 {
1152   const char *ptr_cookie1, *ptr_cookie2;
1153   const char *pattern1 = "_FILE_VERSION_";
1154   const char *pattern2 = "?.?";
1155   const int len_cookie = strlen(cookie);
1156   const int len_pattern1 = strlen(pattern1);
1157   const int len_pattern2 = strlen(pattern2);
1158   const int len_pattern = len_pattern1 + len_pattern2;
1159   int version_major, version_minor;
1160
1161   if (len_cookie <= len_pattern)
1162     return -1;
1163
1164   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1165   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1166
1167   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1168     return -1;
1169
1170   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1171       ptr_cookie2[1] != '.' ||
1172       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1173     return -1;
1174
1175   version_major = ptr_cookie2[0] - '0';
1176   version_minor = ptr_cookie2[2] - '0';
1177
1178   return VERSION_IDENT(version_major, version_minor, 0, 0);
1179 }
1180
1181 boolean checkCookieString(const char *cookie, const char *template)
1182 {
1183   const char *pattern = "_FILE_VERSION_?.?";
1184   const int len_cookie = strlen(cookie);
1185   const int len_template = strlen(template);
1186   const int len_pattern = strlen(pattern);
1187
1188   if (len_cookie != len_template)
1189     return FALSE;
1190
1191   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1192     return FALSE;
1193
1194   return TRUE;
1195 }
1196
1197 /* ------------------------------------------------------------------------- */
1198 /* setup file list and hash handling functions                               */
1199 /* ------------------------------------------------------------------------- */
1200
1201 char *getFormattedSetupEntry(char *token, char *value)
1202 {
1203   int i;
1204   static char entry[MAX_LINE_LEN];
1205
1206   /* if value is an empty string, just return token without value */
1207   if (*value == '\0')
1208     return token;
1209
1210   /* start with the token and some spaces to format output line */
1211   sprintf(entry, "%s:", token);
1212   for (i = strlen(entry); i < token_value_position; i++)
1213     strcat(entry, " ");
1214
1215   /* continue with the token's value */
1216   strcat(entry, value);
1217
1218   return entry;
1219 }
1220
1221 SetupFileList *newSetupFileList(char *token, char *value)
1222 {
1223   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1224
1225   new->token = getStringCopy(token);
1226   new->value = getStringCopy(value);
1227
1228   new->next = NULL;
1229
1230   return new;
1231 }
1232
1233 void freeSetupFileList(SetupFileList *list)
1234 {
1235   if (list == NULL)
1236     return;
1237
1238   checked_free(list->token);
1239   checked_free(list->value);
1240
1241   if (list->next)
1242     freeSetupFileList(list->next);
1243
1244   free(list);
1245 }
1246
1247 char *getListEntry(SetupFileList *list, char *token)
1248 {
1249   if (list == NULL)
1250     return NULL;
1251
1252   if (strcmp(list->token, token) == 0)
1253     return list->value;
1254   else
1255     return getListEntry(list->next, token);
1256 }
1257
1258 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1259 {
1260   if (list == NULL)
1261     return NULL;
1262
1263   if (strcmp(list->token, token) == 0)
1264   {
1265     checked_free(list->value);
1266
1267     list->value = getStringCopy(value);
1268
1269     return list;
1270   }
1271   else if (list->next == NULL)
1272     return (list->next = newSetupFileList(token, value));
1273   else
1274     return setListEntry(list->next, token, value);
1275 }
1276
1277 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1278 {
1279   if (list == NULL)
1280     return NULL;
1281
1282   if (list->next == NULL)
1283     return (list->next = newSetupFileList(token, value));
1284   else
1285     return addListEntry(list->next, token, value);
1286 }
1287
1288 #ifdef DEBUG
1289 static void printSetupFileList(SetupFileList *list)
1290 {
1291   if (!list)
1292     return;
1293
1294   printf("token: '%s'\n", list->token);
1295   printf("value: '%s'\n", list->value);
1296
1297   printSetupFileList(list->next);
1298 }
1299 #endif
1300
1301 #ifdef DEBUG
1302 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1303 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1304 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1305 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1306 #else
1307 #define insert_hash_entry hashtable_insert
1308 #define search_hash_entry hashtable_search
1309 #define change_hash_entry hashtable_change
1310 #define remove_hash_entry hashtable_remove
1311 #endif
1312
1313 static unsigned int get_hash_from_key(void *key)
1314 {
1315   /*
1316     djb2
1317
1318     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1319     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1320     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1321     it works better than many other constants, prime or not) has never been
1322     adequately explained.
1323
1324     If you just want to have a good hash function, and cannot wait, djb2
1325     is one of the best string hash functions i know. It has excellent
1326     distribution and speed on many different sets of keys and table sizes.
1327     You are not likely to do better with one of the "well known" functions
1328     such as PJW, K&R, etc.
1329
1330     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1331   */
1332
1333   char *str = (char *)key;
1334   unsigned int hash = 5381;
1335   int c;
1336
1337   while ((c = *str++))
1338     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1339
1340   return hash;
1341 }
1342
1343 static int keys_are_equal(void *key1, void *key2)
1344 {
1345   return (strcmp((char *)key1, (char *)key2) == 0);
1346 }
1347
1348 SetupFileHash *newSetupFileHash()
1349 {
1350   SetupFileHash *new_hash =
1351     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1352
1353   if (new_hash == NULL)
1354     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1355
1356   return new_hash;
1357 }
1358
1359 void freeSetupFileHash(SetupFileHash *hash)
1360 {
1361   if (hash == NULL)
1362     return;
1363
1364   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1365 }
1366
1367 char *getHashEntry(SetupFileHash *hash, char *token)
1368 {
1369   if (hash == NULL)
1370     return NULL;
1371
1372   return search_hash_entry(hash, token);
1373 }
1374
1375 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1376 {
1377   char *value_copy;
1378
1379   if (hash == NULL)
1380     return;
1381
1382   value_copy = getStringCopy(value);
1383
1384   /* change value; if it does not exist, insert it as new */
1385   if (!change_hash_entry(hash, token, value_copy))
1386     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1387       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1388 }
1389
1390 char *removeHashEntry(SetupFileHash *hash, char *token)
1391 {
1392   if (hash == NULL)
1393     return NULL;
1394
1395   return remove_hash_entry(hash, token);
1396 }
1397
1398 #if 0
1399 #ifdef DEBUG
1400 static void printSetupFileHash(SetupFileHash *hash)
1401 {
1402   BEGIN_HASH_ITERATION(hash, itr)
1403   {
1404     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1405     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1406   }
1407   END_HASH_ITERATION(hash, itr)
1408 }
1409 #endif
1410 #endif
1411
1412 static void *loadSetupFileData(char *filename, boolean use_hash)
1413 {
1414   char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1415   char *token, *value, *line_ptr;
1416   void *setup_file_data, *insert_ptr = NULL;
1417   boolean read_continued_line = FALSE;
1418   FILE *file;
1419
1420   if (!(file = fopen(filename, MODE_READ)))
1421   {
1422     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1423
1424     return NULL;
1425   }
1426
1427   if (use_hash)
1428     setup_file_data = newSetupFileHash();
1429   else
1430     insert_ptr = setup_file_data = newSetupFileList("", "");
1431
1432   while (!feof(file))
1433   {
1434     /* read next line of input file */
1435     if (!fgets(line, MAX_LINE_LEN, file))
1436       break;
1437
1438     /* cut trailing newline or carriage return */
1439     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1440       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1441         *line_ptr = '\0';
1442
1443     if (read_continued_line)
1444     {
1445       /* cut leading whitespaces from input line */
1446       for (line_ptr = line; *line_ptr; line_ptr++)
1447         if (*line_ptr != ' ' && *line_ptr != '\t')
1448           break;
1449
1450       /* append new line to existing line, if there is enough space */
1451       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1452         strcat(previous_line, line_ptr);
1453
1454       strcpy(line, previous_line);      /* copy storage buffer to line */
1455
1456       read_continued_line = FALSE;
1457     }
1458
1459     /* if the last character is '\', continue at next line */
1460     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1461     {
1462       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
1463       strcpy(previous_line, line);      /* copy line to storage buffer */
1464
1465       read_continued_line = TRUE;
1466
1467       continue;
1468     }
1469
1470     /* cut trailing comment from input line */
1471     for (line_ptr = line; *line_ptr; line_ptr++)
1472     {
1473       if (*line_ptr == '#')
1474       {
1475         *line_ptr = '\0';
1476         break;
1477       }
1478     }
1479
1480     /* cut trailing whitespaces from input line */
1481     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1482       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1483         *line_ptr = '\0';
1484
1485     /* ignore empty lines */
1486     if (*line == '\0')
1487       continue;
1488
1489     /* cut leading whitespaces from token */
1490     for (token = line; *token; token++)
1491       if (*token != ' ' && *token != '\t')
1492         break;
1493
1494     /* start with empty value as reliable default */
1495     value = "";
1496
1497     /* find end of token to determine start of value */
1498     for (line_ptr = token; *line_ptr; line_ptr++)
1499     {
1500       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1501       {
1502         *line_ptr = '\0';               /* terminate token string */
1503         value = line_ptr + 1;           /* set beginning of value */
1504
1505         break;
1506       }
1507     }
1508
1509     /* cut leading whitespaces from value */
1510     for (; *value; value++)
1511       if (*value != ' ' && *value != '\t')
1512         break;
1513
1514 #if 0
1515     if (*value == '\0')
1516       value = "true";   /* treat tokens without value as "true" */
1517 #endif
1518
1519     if (*token)
1520     {
1521       if (use_hash)
1522         setHashEntry((SetupFileHash *)setup_file_data, token, value);
1523       else
1524         insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1525     }
1526   }
1527
1528   fclose(file);
1529
1530   if (use_hash)
1531   {
1532     if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1533       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1534   }
1535   else
1536   {
1537     SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1538     SetupFileList *first_valid_list_entry = setup_file_list->next;
1539
1540     /* free empty list header */
1541     setup_file_list->next = NULL;
1542     freeSetupFileList(setup_file_list);
1543     setup_file_data = first_valid_list_entry;
1544
1545     if (first_valid_list_entry == NULL)
1546       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1547   }
1548
1549   return setup_file_data;
1550 }
1551
1552 SetupFileList *loadSetupFileList(char *filename)
1553 {
1554   return (SetupFileList *)loadSetupFileData(filename, FALSE);
1555 }
1556
1557 SetupFileHash *loadSetupFileHash(char *filename)
1558 {
1559   return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1560 }
1561
1562 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1563                                   char *identifier)
1564 {
1565   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1566
1567   if (value == NULL)
1568     Error(ERR_WARN, "configuration file has no file identifier");
1569   else if (!checkCookieString(value, identifier))
1570     Error(ERR_WARN, "configuration file has wrong file identifier");
1571 }
1572
1573
1574 /* ========================================================================= */
1575 /* setup file stuff                                                          */
1576 /* ========================================================================= */
1577
1578 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1579 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1580 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1581
1582 /* level directory info */
1583 #define LEVELINFO_TOKEN_IDENTIFIER      0
1584 #define LEVELINFO_TOKEN_NAME            1
1585 #define LEVELINFO_TOKEN_NAME_SORTING    2
1586 #define LEVELINFO_TOKEN_AUTHOR          3
1587 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1588 #define LEVELINFO_TOKEN_IMPORTED_BY     5
1589 #define LEVELINFO_TOKEN_LEVELS          6
1590 #define LEVELINFO_TOKEN_FIRST_LEVEL     7
1591 #define LEVELINFO_TOKEN_SORT_PRIORITY   8
1592 #define LEVELINFO_TOKEN_LATEST_ENGINE   9
1593 #define LEVELINFO_TOKEN_LEVEL_GROUP     10
1594 #define LEVELINFO_TOKEN_READONLY        11
1595 #define LEVELINFO_TOKEN_GRAPHICS_SET    12
1596 #define LEVELINFO_TOKEN_SOUNDS_SET      13
1597 #define LEVELINFO_TOKEN_MUSIC_SET       14
1598 #define LEVELINFO_TOKEN_FILENAME        15
1599 #define LEVELINFO_TOKEN_FILETYPE        16
1600 #define LEVELINFO_TOKEN_HANDICAP        17
1601 #define LEVELINFO_TOKEN_SKIP_LEVELS     18
1602
1603 #define NUM_LEVELINFO_TOKENS            19
1604
1605 static LevelDirTree ldi;
1606
1607 static struct TokenInfo levelinfo_tokens[] =
1608 {
1609   /* level directory info */
1610   { TYPE_STRING,        &ldi.identifier,        "identifier"    },
1611   { TYPE_STRING,        &ldi.name,              "name"          },
1612   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"  },
1613   { TYPE_STRING,        &ldi.author,            "author"        },
1614   { TYPE_STRING,        &ldi.imported_from,     "imported_from" },
1615   { TYPE_STRING,        &ldi.imported_by,       "imported_by"   },
1616   { TYPE_INTEGER,       &ldi.levels,            "levels"        },
1617   { TYPE_INTEGER,       &ldi.first_level,       "first_level"   },
1618   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority" },
1619   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine" },
1620   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"   },
1621   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"      },
1622   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"  },
1623   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"    },
1624   { TYPE_STRING,        &ldi.music_set,         "music_set"     },
1625   { TYPE_STRING,        &ldi.level_filename,    "filename"      },
1626   { TYPE_STRING,        &ldi.level_filetype,    "filetype"      },
1627   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"      },
1628   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"   }
1629 };
1630
1631 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1632 {
1633   ldi->type = type;
1634
1635   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1636                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1637                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1638                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1639                    NULL);
1640
1641   ldi->node_parent = NULL;
1642   ldi->node_group = NULL;
1643   ldi->next = NULL;
1644
1645   ldi->cl_first = -1;
1646   ldi->cl_cursor = -1;
1647
1648   ldi->subdir = NULL;
1649   ldi->fullpath = NULL;
1650   ldi->basepath = NULL;
1651   ldi->identifier = NULL;
1652   ldi->name = getStringCopy(ANONYMOUS_NAME);
1653   ldi->name_sorting = NULL;
1654   ldi->author = getStringCopy(ANONYMOUS_NAME);
1655
1656   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1657   ldi->latest_engine = FALSE;                   /* default: get from level */
1658   ldi->parent_link = FALSE;
1659   ldi->in_user_dir = FALSE;
1660   ldi->user_defined = FALSE;
1661   ldi->color = 0;
1662   ldi->class_desc = NULL;
1663
1664   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1665   {
1666     ldi->imported_from = NULL;
1667     ldi->imported_by = NULL;
1668
1669     ldi->graphics_set = NULL;
1670     ldi->sounds_set = NULL;
1671     ldi->music_set = NULL;
1672     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1673     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1674     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1675
1676     ldi->level_filename = NULL;
1677     ldi->level_filetype = NULL;
1678
1679     ldi->levels = 0;
1680     ldi->first_level = 0;
1681     ldi->last_level = 0;
1682     ldi->level_group = FALSE;
1683     ldi->handicap_level = 0;
1684     ldi->readonly = TRUE;
1685     ldi->handicap = TRUE;
1686     ldi->skip_levels = FALSE;
1687   }
1688 }
1689
1690 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1691 {
1692   if (parent == NULL)
1693   {
1694     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1695
1696     setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
1697
1698     return;
1699   }
1700
1701 #if 1
1702   /* copy all values from the parent structure */
1703
1704   ldi->type = parent->type;
1705
1706   ldi->node_top = parent->node_top;
1707   ldi->node_parent = parent;
1708   ldi->node_group = NULL;
1709   ldi->next = NULL;
1710
1711   ldi->cl_first = -1;
1712   ldi->cl_cursor = -1;
1713
1714   ldi->subdir = NULL;
1715   ldi->fullpath = NULL;
1716   ldi->basepath = NULL;
1717   ldi->identifier = NULL;
1718   ldi->name = getStringCopy(ANONYMOUS_NAME);
1719   ldi->name_sorting = NULL;
1720   ldi->author = getStringCopy(parent->author);
1721
1722   ldi->sort_priority = parent->sort_priority;
1723   ldi->latest_engine = parent->latest_engine;
1724   ldi->parent_link = FALSE;
1725   ldi->in_user_dir = parent->in_user_dir;
1726   ldi->user_defined = parent->user_defined;
1727   ldi->color = parent->color;
1728   ldi->class_desc = getStringCopy(parent->class_desc);
1729
1730   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1731   {
1732     ldi->imported_from = getStringCopy(parent->imported_from);
1733     ldi->imported_by = getStringCopy(parent->imported_by);
1734
1735     ldi->graphics_set = NULL;
1736     ldi->sounds_set = NULL;
1737     ldi->music_set = NULL;
1738     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1739     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1740     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1741
1742     ldi->level_filename = NULL;
1743     ldi->level_filetype = NULL;
1744
1745     ldi->levels = 0;
1746     ldi->first_level = 0;
1747     ldi->last_level = 0;
1748     ldi->level_group = FALSE;
1749     ldi->handicap_level = 0;
1750     ldi->readonly = TRUE;
1751     ldi->handicap = TRUE;
1752     ldi->skip_levels = FALSE;
1753   }
1754
1755 #else
1756
1757   /* first copy all values from the parent structure ... */
1758   *ldi = *parent;
1759
1760   /* ... then set all fields to default that cannot be inherited from parent.
1761      This is especially important for all those fields that can be set from
1762      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1763      calls 'free()' for all already set token values which requires that no
1764      other structure's pointer may point to them!
1765   */
1766
1767   ldi->subdir = NULL;
1768   ldi->fullpath = NULL;
1769   ldi->basepath = NULL;
1770   ldi->identifier = NULL;
1771   ldi->name = getStringCopy(ANONYMOUS_NAME);
1772   ldi->name_sorting = NULL;
1773   ldi->author = getStringCopy(parent->author);
1774
1775   ldi->imported_from = getStringCopy(parent->imported_from);
1776   ldi->imported_by = getStringCopy(parent->imported_by);
1777   ldi->class_desc = getStringCopy(parent->class_desc);
1778
1779   ldi->graphics_set = NULL;
1780   ldi->sounds_set = NULL;
1781   ldi->music_set = NULL;
1782   ldi->graphics_path = NULL;
1783   ldi->sounds_path = NULL;
1784   ldi->music_path = NULL;
1785
1786   ldi->level_group = FALSE;
1787   ldi->parent_link = FALSE;
1788
1789   ldi->node_top = parent->node_top;
1790   ldi->node_parent = parent;
1791   ldi->node_group = NULL;
1792   ldi->next = NULL;
1793
1794 #endif
1795 }
1796
1797 static void freeTreeInfo(TreeInfo *ldi)
1798 {
1799   checked_free(ldi->subdir);
1800   checked_free(ldi->fullpath);
1801   checked_free(ldi->basepath);
1802   checked_free(ldi->identifier);
1803
1804   checked_free(ldi->name);
1805   checked_free(ldi->name_sorting);
1806   checked_free(ldi->author);
1807
1808   checked_free(ldi->class_desc);
1809
1810   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1811   {
1812     checked_free(ldi->imported_from);
1813     checked_free(ldi->imported_by);
1814
1815     checked_free(ldi->graphics_set);
1816     checked_free(ldi->sounds_set);
1817     checked_free(ldi->music_set);
1818
1819     checked_free(ldi->graphics_path);
1820     checked_free(ldi->sounds_path);
1821     checked_free(ldi->music_path);
1822
1823     checked_free(ldi->level_filename);
1824     checked_free(ldi->level_filetype);
1825   }
1826 }
1827
1828 void setSetupInfo(struct TokenInfo *token_info,
1829                   int token_nr, char *token_value)
1830 {
1831   int token_type = token_info[token_nr].type;
1832   void *setup_value = token_info[token_nr].value;
1833
1834   if (token_value == NULL)
1835     return;
1836
1837   /* set setup field to corresponding token value */
1838   switch (token_type)
1839   {
1840     case TYPE_BOOLEAN:
1841     case TYPE_SWITCH:
1842       *(boolean *)setup_value = get_boolean_from_string(token_value);
1843       break;
1844
1845     case TYPE_KEY:
1846       *(Key *)setup_value = getKeyFromKeyName(token_value);
1847       break;
1848
1849     case TYPE_KEY_X11:
1850       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1851       break;
1852
1853     case TYPE_INTEGER:
1854       *(int *)setup_value = get_integer_from_string(token_value);
1855       break;
1856
1857     case TYPE_STRING:
1858       checked_free(*(char **)setup_value);
1859       *(char **)setup_value = getStringCopy(token_value);
1860       break;
1861
1862     default:
1863       break;
1864   }
1865 }
1866
1867 static int compareTreeInfoEntries(const void *object1, const void *object2)
1868 {
1869   const TreeInfo *entry1 = *((TreeInfo **)object1);
1870   const TreeInfo *entry2 = *((TreeInfo **)object2);
1871   int class_sorting1, class_sorting2;
1872   int compare_result;
1873
1874   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1875   {
1876     class_sorting1 = LEVELSORTING(entry1);
1877     class_sorting2 = LEVELSORTING(entry2);
1878   }
1879   else
1880   {
1881     class_sorting1 = ARTWORKSORTING(entry1);
1882     class_sorting2 = ARTWORKSORTING(entry2);
1883   }
1884
1885   if (entry1->parent_link || entry2->parent_link)
1886     compare_result = (entry1->parent_link ? -1 : +1);
1887   else if (entry1->sort_priority == entry2->sort_priority)
1888   {
1889     char *name1 = getStringToLower(entry1->name_sorting);
1890     char *name2 = getStringToLower(entry2->name_sorting);
1891
1892     compare_result = strcmp(name1, name2);
1893
1894     free(name1);
1895     free(name2);
1896   }
1897   else if (class_sorting1 == class_sorting2)
1898     compare_result = entry1->sort_priority - entry2->sort_priority;
1899   else
1900     compare_result = class_sorting1 - class_sorting2;
1901
1902   return compare_result;
1903 }
1904
1905 static void createParentTreeInfoNode(TreeInfo *node_parent)
1906 {
1907   TreeInfo *ti_new;
1908
1909   if (node_parent == NULL)
1910     return;
1911
1912   ti_new = newTreeInfo();
1913   setTreeInfoToDefaults(ti_new, node_parent->type);
1914
1915   ti_new->node_parent = node_parent;
1916   ti_new->parent_link = TRUE;
1917
1918 #if 1
1919   setString(&ti_new->identifier, node_parent->identifier);
1920   setString(&ti_new->name, ".. (parent directory)");
1921   setString(&ti_new->name_sorting, ti_new->name);
1922
1923   setString(&ti_new->subdir, "..");
1924   setString(&ti_new->fullpath, node_parent->fullpath);
1925
1926   ti_new->sort_priority = node_parent->sort_priority;
1927   ti_new->latest_engine = node_parent->latest_engine;
1928
1929   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
1930 #else
1931   ti_new->identifier = getStringCopy(node_parent->identifier);
1932   ti_new->name = ".. (parent directory)";
1933   ti_new->name_sorting = getStringCopy(ti_new->name);
1934
1935   ti_new->subdir = "..";
1936   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1937
1938   ti_new->sort_priority = node_parent->sort_priority;
1939   ti_new->latest_engine = node_parent->latest_engine;
1940
1941   ti_new->class_desc = getLevelClassDescription(ti_new);
1942 #endif
1943
1944   pushTreeInfo(&node_parent->node_group, ti_new);
1945 }
1946
1947 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1948 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1949
1950 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1951                                           TreeInfo *node_parent,
1952                                           char *level_directory,
1953                                           char *directory_name)
1954 {
1955   char *directory_path = getPath2(level_directory, directory_name);
1956   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1957   SetupFileHash *setup_file_hash;
1958   LevelDirTree *leveldir_new = NULL;
1959   int i;
1960
1961   /* unless debugging, silently ignore directories without "levelinfo.conf" */
1962   if (!options.debug && !fileExists(filename))
1963   {
1964     free(directory_path);
1965     free(filename);
1966
1967     return FALSE;
1968   }
1969
1970   setup_file_hash = loadSetupFileHash(filename);
1971
1972   if (setup_file_hash == NULL)
1973   {
1974     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1975
1976     free(directory_path);
1977     free(filename);
1978
1979     return FALSE;
1980   }
1981
1982   leveldir_new = newTreeInfo();
1983
1984   if (node_parent)
1985     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1986   else
1987     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1988
1989   leveldir_new->subdir = getStringCopy(directory_name);
1990
1991   checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1992
1993   /* set all structure fields according to the token/value pairs */
1994   ldi = *leveldir_new;
1995   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
1996     setSetupInfo(levelinfo_tokens, i,
1997                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1998   *leveldir_new = ldi;
1999
2000 #if 1
2001   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
2002     setString(&leveldir_new->name, leveldir_new->subdir);
2003 #else
2004   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
2005   {
2006     free(leveldir_new->name);
2007     leveldir_new->name = getStringCopy(leveldir_new->subdir);
2008   }
2009 #endif
2010
2011   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2012
2013   if (leveldir_new->identifier == NULL)
2014     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2015
2016   if (leveldir_new->name_sorting == NULL)
2017     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2018
2019   if (node_parent == NULL)              /* top level group */
2020   {
2021     leveldir_new->basepath = getStringCopy(level_directory);
2022     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2023   }
2024   else                                  /* sub level group */
2025   {
2026     leveldir_new->basepath = getStringCopy(node_parent->basepath);
2027     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2028   }
2029
2030   if (leveldir_new->levels < 1)
2031     leveldir_new->levels = 1;
2032
2033   leveldir_new->last_level =
2034     leveldir_new->first_level + leveldir_new->levels - 1;
2035
2036 #if 1
2037   leveldir_new->in_user_dir =
2038     (strcmp(leveldir_new->basepath, options.level_directory) != 0);
2039 #else
2040   leveldir_new->in_user_dir =
2041     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
2042 #endif
2043
2044 #if 1
2045   /* adjust some settings if user's private level directory was detected */
2046   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2047       leveldir_new->in_user_dir &&
2048       (strcmp(leveldir_new->subdir, getLoginName()) == 0 ||
2049        strcmp(leveldir_new->name,   getLoginName()) == 0 ||
2050        strcmp(leveldir_new->author, getRealName())  == 0))
2051   {
2052     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2053     leveldir_new->readonly = FALSE;
2054   }
2055
2056 #else
2057   /* adjust sort priority if user's private level directory was detected */
2058   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2059       leveldir_new->in_user_dir &&
2060       strcmp(leveldir_new->subdir, getLoginName()) == 0)
2061     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2062 #endif
2063
2064   leveldir_new->user_defined =
2065     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2066
2067   leveldir_new->color = LEVELCOLOR(leveldir_new);
2068 #if 1
2069   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2070 #else
2071   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
2072 #endif
2073
2074   leveldir_new->handicap_level =        /* set handicap to default value */
2075     (leveldir_new->user_defined || !leveldir_new->handicap ?
2076      leveldir_new->last_level : leveldir_new->first_level);
2077
2078   pushTreeInfo(node_first, leveldir_new);
2079
2080   freeSetupFileHash(setup_file_hash);
2081
2082   if (leveldir_new->level_group)
2083   {
2084     /* create node to link back to current level directory */
2085     createParentTreeInfoNode(leveldir_new);
2086
2087     /* step into sub-directory and look for more level series */
2088     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2089                               leveldir_new, directory_path);
2090   }
2091
2092   free(directory_path);
2093   free(filename);
2094
2095   return TRUE;
2096 }
2097
2098 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2099                                       TreeInfo *node_parent,
2100                                       char *level_directory)
2101 {
2102   DIR *dir;
2103   struct dirent *dir_entry;
2104   boolean valid_entry_found = FALSE;
2105
2106   if ((dir = opendir(level_directory)) == NULL)
2107   {
2108     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2109     return;
2110   }
2111
2112   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2113   {
2114     struct stat file_status;
2115     char *directory_name = dir_entry->d_name;
2116     char *directory_path = getPath2(level_directory, directory_name);
2117
2118     /* skip entries for current and parent directory */
2119     if (strcmp(directory_name, ".")  == 0 ||
2120         strcmp(directory_name, "..") == 0)
2121     {
2122       free(directory_path);
2123       continue;
2124     }
2125
2126     /* find out if directory entry is itself a directory */
2127     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2128         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2129     {
2130       free(directory_path);
2131       continue;
2132     }
2133
2134     free(directory_path);
2135
2136     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
2137         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
2138         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
2139       continue;
2140
2141     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2142                                                     level_directory,
2143                                                     directory_name);
2144   }
2145
2146   closedir(dir);
2147
2148   if (!valid_entry_found)
2149   {
2150     /* check if this directory directly contains a file "levelinfo.conf" */
2151     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2152                                                     level_directory, ".");
2153   }
2154
2155   if (!valid_entry_found)
2156     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2157           level_directory);
2158 }
2159
2160 void LoadLevelInfo()
2161 {
2162   InitUserLevelDirectory(getLoginName());
2163
2164   DrawInitText("Loading level series:", 120, FC_GREEN);
2165
2166   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2167   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2168
2169   /* before sorting, the first entries will be from the user directory */
2170   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2171
2172   if (leveldir_first == NULL)
2173     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2174
2175   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
2176
2177 #if 0
2178   dumpTreeInfo(leveldir_first, 0);
2179 #endif
2180 }
2181
2182 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2183                                               TreeInfo *node_parent,
2184                                               char *base_directory,
2185                                               char *directory_name, int type)
2186 {
2187   char *directory_path = getPath2(base_directory, directory_name);
2188   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2189   SetupFileHash *setup_file_hash = NULL;
2190   TreeInfo *artwork_new = NULL;
2191   int i;
2192
2193   if (fileExists(filename))
2194     setup_file_hash = loadSetupFileHash(filename);
2195
2196   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2197   {
2198     DIR *dir;
2199     struct dirent *dir_entry;
2200     boolean valid_file_found = FALSE;
2201
2202     if ((dir = opendir(directory_path)) != NULL)
2203     {
2204       while ((dir_entry = readdir(dir)) != NULL)
2205       {
2206         char *entry_name = dir_entry->d_name;
2207
2208         if (FileIsArtworkType(entry_name, type))
2209         {
2210           valid_file_found = TRUE;
2211           break;
2212         }
2213       }
2214
2215       closedir(dir);
2216     }
2217
2218     if (!valid_file_found)
2219     {
2220       if (strcmp(directory_name, ".") != 0)
2221         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2222
2223       free(directory_path);
2224       free(filename);
2225
2226       return FALSE;
2227     }
2228   }
2229
2230   artwork_new = newTreeInfo();
2231
2232   if (node_parent)
2233     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2234   else
2235     setTreeInfoToDefaults(artwork_new, type);
2236
2237   artwork_new->subdir = getStringCopy(directory_name);
2238
2239   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2240   {
2241 #if 0
2242     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
2243 #endif
2244
2245     /* set all structure fields according to the token/value pairs */
2246     ldi = *artwork_new;
2247     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2248       setSetupInfo(levelinfo_tokens, i,
2249                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2250     *artwork_new = ldi;
2251
2252 #if 1
2253     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2254       setString(&artwork_new->name, artwork_new->subdir);
2255 #else
2256     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2257     {
2258       free(artwork_new->name);
2259       artwork_new->name = getStringCopy(artwork_new->subdir);
2260     }
2261 #endif
2262
2263 #if 0
2264     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2265 #endif
2266
2267     if (artwork_new->identifier == NULL)
2268       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2269
2270     if (artwork_new->name_sorting == NULL)
2271       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2272   }
2273
2274   if (node_parent == NULL)              /* top level group */
2275   {
2276     artwork_new->basepath = getStringCopy(base_directory);
2277     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2278   }
2279   else                                  /* sub level group */
2280   {
2281     artwork_new->basepath = getStringCopy(node_parent->basepath);
2282     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2283   }
2284
2285 #if 1
2286   artwork_new->in_user_dir =
2287     (strcmp(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)) != 0);
2288 #else
2289   artwork_new->in_user_dir =
2290     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
2291 #endif
2292
2293   /* (may use ".sort_priority" from "setup_file_hash" above) */
2294   artwork_new->color = ARTWORKCOLOR(artwork_new);
2295 #if 1
2296   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2297 #else
2298   artwork_new->class_desc = getLevelClassDescription(artwork_new);
2299 #endif
2300
2301   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2302   {
2303 #if 0
2304     if (artwork_new->name != NULL)
2305     {
2306       free(artwork_new->name);
2307       artwork_new->name = NULL;
2308     }
2309 #endif
2310
2311 #if 0
2312     if (artwork_new->identifier != NULL)
2313     {
2314       free(artwork_new->identifier);
2315       artwork_new->identifier = NULL;
2316     }
2317 #endif
2318
2319     if (strcmp(artwork_new->subdir, ".") == 0)
2320     {
2321       if (artwork_new->user_defined)
2322       {
2323 #if 1
2324         setString(&artwork_new->identifier, "private");
2325 #else
2326         artwork_new->identifier = getStringCopy("private");
2327 #endif
2328         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2329       }
2330       else
2331       {
2332 #if 1
2333         setString(&artwork_new->identifier, "classic");
2334 #else
2335         artwork_new->identifier = getStringCopy("classic");
2336 #endif
2337         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2338       }
2339
2340       /* set to new values after changing ".sort_priority" */
2341       artwork_new->color = ARTWORKCOLOR(artwork_new);
2342 #if 1
2343       setString(&artwork_new->class_desc,
2344                 getLevelClassDescription(artwork_new));
2345 #else
2346       artwork_new->class_desc = getLevelClassDescription(artwork_new);
2347 #endif
2348     }
2349     else
2350     {
2351 #if 1
2352       setString(&artwork_new->identifier, artwork_new->subdir);
2353 #else
2354       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2355 #endif
2356     }
2357
2358 #if 1
2359     setString(&artwork_new->name, artwork_new->identifier);
2360     setString(&artwork_new->name_sorting, artwork_new->name);
2361 #else
2362     artwork_new->name = getStringCopy(artwork_new->identifier);
2363     artwork_new->name_sorting = getStringCopy(artwork_new->name);
2364 #endif
2365   }
2366
2367   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2368
2369   pushTreeInfo(node_first, artwork_new);
2370
2371   freeSetupFileHash(setup_file_hash);
2372
2373   free(directory_path);
2374   free(filename);
2375
2376   return TRUE;
2377 }
2378
2379 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2380                                           TreeInfo *node_parent,
2381                                           char *base_directory, int type)
2382 {
2383   DIR *dir;
2384   struct dirent *dir_entry;
2385   boolean valid_entry_found = FALSE;
2386
2387   if ((dir = opendir(base_directory)) == NULL)
2388   {
2389     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2390       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2391     return;
2392   }
2393
2394   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2395   {
2396     struct stat file_status;
2397     char *directory_name = dir_entry->d_name;
2398     char *directory_path = getPath2(base_directory, directory_name);
2399
2400     /* skip entries for current and parent directory */
2401     if (strcmp(directory_name, ".")  == 0 ||
2402         strcmp(directory_name, "..") == 0)
2403     {
2404       free(directory_path);
2405       continue;
2406     }
2407
2408     /* find out if directory entry is itself a directory */
2409     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2410         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2411     {
2412       free(directory_path);
2413       continue;
2414     }
2415
2416     free(directory_path);
2417
2418     /* check if this directory contains artwork with or without config file */
2419     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2420                                                         base_directory,
2421                                                         directory_name, type);
2422   }
2423
2424   closedir(dir);
2425
2426   /* check if this directory directly contains artwork itself */
2427   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2428                                                       base_directory, ".",
2429                                                       type);
2430   if (!valid_entry_found)
2431     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2432           base_directory);
2433 }
2434
2435 static TreeInfo *getDummyArtworkInfo(int type)
2436 {
2437   /* this is only needed when there is completely no artwork available */
2438   TreeInfo *artwork_new = newTreeInfo();
2439
2440   setTreeInfoToDefaults(artwork_new, type);
2441
2442 #if 1
2443   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
2444   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2445   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2446
2447   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2448   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2449   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2450 #else
2451   artwork_new->subdir   = getStringCopy(UNDEFINED_FILENAME);
2452   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2453   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2454
2455   checked_free(artwork_new->name);
2456
2457   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
2458   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
2459   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2460 #endif
2461
2462   return artwork_new;
2463 }
2464
2465 void LoadArtworkInfo()
2466 {
2467   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2468
2469   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2470                                 options.graphics_directory,
2471                                 TREE_TYPE_GRAPHICS_DIR);
2472   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2473                                 getUserGraphicsDir(),
2474                                 TREE_TYPE_GRAPHICS_DIR);
2475
2476   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2477                                 options.sounds_directory,
2478                                 TREE_TYPE_SOUNDS_DIR);
2479   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2480                                 getUserSoundsDir(),
2481                                 TREE_TYPE_SOUNDS_DIR);
2482
2483   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2484                                 options.music_directory,
2485                                 TREE_TYPE_MUSIC_DIR);
2486   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2487                                 getUserMusicDir(),
2488                                 TREE_TYPE_MUSIC_DIR);
2489
2490   if (artwork.gfx_first == NULL)
2491     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2492   if (artwork.snd_first == NULL)
2493     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2494   if (artwork.mus_first == NULL)
2495     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2496
2497   /* before sorting, the first entries will be from the user directory */
2498   artwork.gfx_current =
2499     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2500   if (artwork.gfx_current == NULL)
2501     artwork.gfx_current =
2502       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2503   if (artwork.gfx_current == NULL)
2504     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2505
2506   artwork.snd_current =
2507     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2508   if (artwork.snd_current == NULL)
2509     artwork.snd_current =
2510       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2511   if (artwork.snd_current == NULL)
2512     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2513
2514   artwork.mus_current =
2515     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2516   if (artwork.mus_current == NULL)
2517     artwork.mus_current =
2518       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2519   if (artwork.mus_current == NULL)
2520     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2521
2522   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2523   artwork.snd_current_identifier = artwork.snd_current->identifier;
2524   artwork.mus_current_identifier = artwork.mus_current->identifier;
2525
2526 #if 0
2527   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2528   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2529   printf("music set == %s\n\n", artwork.mus_current_identifier);
2530 #endif
2531
2532   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2533   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2534   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2535
2536 #if 0
2537   dumpTreeInfo(artwork.gfx_first, 0);
2538   dumpTreeInfo(artwork.snd_first, 0);
2539   dumpTreeInfo(artwork.mus_first, 0);
2540 #endif
2541 }
2542
2543 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2544                                   LevelDirTree *level_node)
2545 {
2546   /* recursively check all level directories for artwork sub-directories */
2547
2548   while (level_node)
2549   {
2550     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2551                           ARTWORK_DIRECTORY((*artwork_node)->type));
2552
2553 #if 0
2554     if (!level_node->parent_link)
2555       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2556              level_node->subdir, level_node->name);
2557 #endif
2558
2559     if (!level_node->parent_link)
2560     {
2561       TreeInfo *topnode_last = *artwork_node;
2562
2563       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2564                                     (*artwork_node)->type);
2565
2566       if (topnode_last != *artwork_node)
2567       {
2568         free((*artwork_node)->identifier);
2569         free((*artwork_node)->name);
2570         free((*artwork_node)->name_sorting);
2571
2572         (*artwork_node)->identifier   = getStringCopy(level_node->subdir);
2573         (*artwork_node)->name         = getStringCopy(level_node->name);
2574         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2575
2576         (*artwork_node)->sort_priority = level_node->sort_priority;
2577         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2578       }
2579     }
2580
2581     free(path);
2582
2583     if (level_node->node_group != NULL)
2584       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2585
2586     level_node = level_node->next;
2587   }
2588 }
2589
2590 void LoadLevelArtworkInfo()
2591 {
2592   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2593
2594   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2595   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2596   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2597
2598   /* needed for reloading level artwork not known at ealier stage */
2599
2600   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2601   {
2602     artwork.gfx_current =
2603       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2604     if (artwork.gfx_current == NULL)
2605       artwork.gfx_current =
2606         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2607     if (artwork.gfx_current == NULL)
2608       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2609   }
2610
2611   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2612   {
2613     artwork.snd_current =
2614       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2615     if (artwork.snd_current == NULL)
2616       artwork.snd_current =
2617         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2618     if (artwork.snd_current == NULL)
2619       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2620   }
2621
2622   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2623   {
2624     artwork.mus_current =
2625       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2626     if (artwork.mus_current == NULL)
2627       artwork.mus_current =
2628         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2629     if (artwork.mus_current == NULL)
2630       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2631   }
2632
2633   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2634   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2635   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2636
2637 #if 0
2638   dumpTreeInfo(artwork.gfx_first, 0);
2639   dumpTreeInfo(artwork.snd_first, 0);
2640   dumpTreeInfo(artwork.mus_first, 0);
2641 #endif
2642 }
2643
2644 static void SaveUserLevelInfo()
2645 {
2646   LevelDirTree *level_info;
2647   char *filename;
2648   FILE *file;
2649   int i;
2650
2651   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2652
2653   if (!(file = fopen(filename, MODE_WRITE)))
2654   {
2655     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2656     free(filename);
2657     return;
2658   }
2659
2660   level_info = newTreeInfo();
2661
2662   /* always start with reliable default values */
2663   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2664
2665 #if 1
2666   setString(&level_info->name, getLoginName());
2667   setString(&level_info->author, getRealName());
2668   level_info->levels = 100;
2669   level_info->first_level = 1;
2670 #if 0
2671   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
2672   level_info->readonly = FALSE;
2673   setString(&level_info->graphics_set, GFX_CLASSIC_SUBDIR);
2674   setString(&level_info->sounds_set,   SND_CLASSIC_SUBDIR);
2675   setString(&level_info->music_set,    MUS_CLASSIC_SUBDIR);
2676 #endif
2677 #else
2678   ldi.name = getStringCopy(getLoginName());
2679   ldi.author = getStringCopy(getRealName());
2680   ldi.levels = 100;
2681   ldi.first_level = 1;
2682   ldi.sort_priority = LEVELCLASS_PRIVATE_START;
2683   ldi.readonly = FALSE;
2684   ldi.graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
2685   ldi.sounds_set = getStringCopy(SND_CLASSIC_SUBDIR);
2686   ldi.music_set = getStringCopy(MUS_CLASSIC_SUBDIR);
2687 #endif
2688
2689   token_value_position = TOKEN_VALUE_POSITION_SHORT;
2690
2691   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2692                                                  getCookie("LEVELINFO")));
2693
2694   ldi = *level_info;
2695   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2696   {
2697 #if 1
2698     if (i == LEVELINFO_TOKEN_NAME ||
2699         i == LEVELINFO_TOKEN_AUTHOR ||
2700         i == LEVELINFO_TOKEN_LEVELS ||
2701         i == LEVELINFO_TOKEN_FIRST_LEVEL)
2702       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2703
2704     /* just to make things nicer :) */
2705     if (i == LEVELINFO_TOKEN_AUTHOR)
2706       fprintf(file, "\n");      
2707 #else
2708     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2709         i != LEVELINFO_TOKEN_NAME_SORTING &&
2710         i != LEVELINFO_TOKEN_IMPORTED_FROM &&
2711         i != LEVELINFO_TOKEN_IMPORTED_BY &&
2712         i != LEVELINFO_TOKEN_FILENAME &&
2713         i != LEVELINFO_TOKEN_FILETYPE)
2714       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2715 #endif
2716   }
2717
2718   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
2719
2720   fclose(file);
2721
2722   SetFilePermissions(filename, PERMS_PRIVATE);
2723
2724   freeTreeInfo(level_info);
2725   free(filename);
2726 }
2727
2728 char *getSetupValue(int type, void *value)
2729 {
2730   static char value_string[MAX_LINE_LEN];
2731
2732   if (value == NULL)
2733     return NULL;
2734
2735   switch (type)
2736   {
2737     case TYPE_BOOLEAN:
2738       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2739       break;
2740
2741     case TYPE_SWITCH:
2742       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2743       break;
2744
2745     case TYPE_YES_NO:
2746       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2747       break;
2748
2749     case TYPE_KEY:
2750       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2751       break;
2752
2753     case TYPE_KEY_X11:
2754       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2755       break;
2756
2757     case TYPE_INTEGER:
2758       sprintf(value_string, "%d", *(int *)value);
2759       break;
2760
2761     case TYPE_STRING:
2762       strcpy(value_string, *(char **)value);
2763       break;
2764
2765     default:
2766       value_string[0] = '\0';
2767       break;
2768   }
2769
2770   return value_string;
2771 }
2772
2773 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2774 {
2775   int i;
2776   char *line;
2777   static char token_string[MAX_LINE_LEN];
2778   int token_type = token_info[token_nr].type;
2779   void *setup_value = token_info[token_nr].value;
2780   char *token_text = token_info[token_nr].text;
2781   char *value_string = getSetupValue(token_type, setup_value);
2782
2783   /* build complete token string */
2784   sprintf(token_string, "%s%s", prefix, token_text);
2785
2786   /* build setup entry line */
2787   line = getFormattedSetupEntry(token_string, value_string);
2788
2789   if (token_type == TYPE_KEY_X11)
2790   {
2791     Key key = *(Key *)setup_value;
2792     char *keyname = getKeyNameFromKey(key);
2793
2794     /* add comment, if useful */
2795     if (strcmp(keyname, "(undefined)") != 0 &&
2796         strcmp(keyname, "(unknown)") != 0)
2797     {
2798       /* add at least one whitespace */
2799       strcat(line, " ");
2800       for (i = strlen(line); i < token_comment_position; i++)
2801         strcat(line, " ");
2802
2803       strcat(line, "# ");
2804       strcat(line, keyname);
2805     }
2806   }
2807
2808   return line;
2809 }
2810
2811 void LoadLevelSetup_LastSeries()
2812 {
2813   /* ----------------------------------------------------------------------- */
2814   /* ~/.<program>/levelsetup.conf                                            */
2815   /* ----------------------------------------------------------------------- */
2816
2817   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2818   SetupFileHash *level_setup_hash = NULL;
2819
2820   /* always start with reliable default values */
2821   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2822
2823   if ((level_setup_hash = loadSetupFileHash(filename)))
2824   {
2825     char *last_level_series =
2826       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2827
2828     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2829                                                  last_level_series);
2830     if (leveldir_current == NULL)
2831       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2832
2833     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2834
2835     freeSetupFileHash(level_setup_hash);
2836   }
2837   else
2838     Error(ERR_WARN, "using default setup values");
2839
2840   free(filename);
2841 }
2842
2843 void SaveLevelSetup_LastSeries()
2844 {
2845   /* ----------------------------------------------------------------------- */
2846   /* ~/.<program>/levelsetup.conf                                            */
2847   /* ----------------------------------------------------------------------- */
2848
2849   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2850   char *level_subdir = leveldir_current->subdir;
2851   FILE *file;
2852
2853   InitUserDataDirectory();
2854
2855   if (!(file = fopen(filename, MODE_WRITE)))
2856   {
2857     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2858     free(filename);
2859     return;
2860   }
2861
2862   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2863                                                  getCookie("LEVELSETUP")));
2864   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2865                                                level_subdir));
2866
2867   fclose(file);
2868
2869   SetFilePermissions(filename, PERMS_PRIVATE);
2870
2871   free(filename);
2872 }
2873
2874 static void checkSeriesInfo()
2875 {
2876   static char *level_directory = NULL;
2877   DIR *dir;
2878   struct dirent *dir_entry;
2879
2880   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2881
2882   level_directory = getPath2((leveldir_current->in_user_dir ?
2883                               getUserLevelDir(NULL) :
2884                               options.level_directory),
2885                              leveldir_current->fullpath);
2886
2887   if ((dir = opendir(level_directory)) == NULL)
2888   {
2889     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2890     return;
2891   }
2892
2893   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2894   {
2895     if (strlen(dir_entry->d_name) > 4 &&
2896         dir_entry->d_name[3] == '.' &&
2897         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2898     {
2899       char levelnum_str[4];
2900       int levelnum_value;
2901
2902       strncpy(levelnum_str, dir_entry->d_name, 3);
2903       levelnum_str[3] = '\0';
2904
2905       levelnum_value = atoi(levelnum_str);
2906
2907 #if 0
2908       if (levelnum_value < leveldir_current->first_level)
2909       {
2910         Error(ERR_WARN, "additional level %d found", levelnum_value);
2911         leveldir_current->first_level = levelnum_value;
2912       }
2913       else if (levelnum_value > leveldir_current->last_level)
2914       {
2915         Error(ERR_WARN, "additional level %d found", levelnum_value);
2916         leveldir_current->last_level = levelnum_value;
2917       }
2918 #endif
2919     }
2920   }
2921
2922   closedir(dir);
2923 }
2924
2925 void LoadLevelSetup_SeriesInfo()
2926 {
2927   char *filename;
2928   SetupFileHash *level_setup_hash = NULL;
2929   char *level_subdir = leveldir_current->subdir;
2930
2931   /* always start with reliable default values */
2932   level_nr = leveldir_current->first_level;
2933
2934   checkSeriesInfo(leveldir_current);
2935
2936   /* ----------------------------------------------------------------------- */
2937   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2938   /* ----------------------------------------------------------------------- */
2939
2940   level_subdir = leveldir_current->subdir;
2941
2942   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2943
2944   if ((level_setup_hash = loadSetupFileHash(filename)))
2945   {
2946     char *token_value;
2947
2948     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2949
2950     if (token_value)
2951     {
2952       level_nr = atoi(token_value);
2953
2954       if (level_nr < leveldir_current->first_level)
2955         level_nr = leveldir_current->first_level;
2956       if (level_nr > leveldir_current->last_level)
2957         level_nr = leveldir_current->last_level;
2958     }
2959
2960     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2961
2962     if (token_value)
2963     {
2964       int level_nr = atoi(token_value);
2965
2966       if (level_nr < leveldir_current->first_level)
2967         level_nr = leveldir_current->first_level;
2968       if (level_nr > leveldir_current->last_level + 1)
2969         level_nr = leveldir_current->last_level;
2970
2971       if (leveldir_current->user_defined || !leveldir_current->handicap)
2972         level_nr = leveldir_current->last_level;
2973
2974       leveldir_current->handicap_level = level_nr;
2975     }
2976
2977     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2978
2979     freeSetupFileHash(level_setup_hash);
2980   }
2981   else
2982     Error(ERR_WARN, "using default setup values");
2983
2984   free(filename);
2985 }
2986
2987 void SaveLevelSetup_SeriesInfo()
2988 {
2989   char *filename;
2990   char *level_subdir = leveldir_current->subdir;
2991   char *level_nr_str = int2str(level_nr, 0);
2992   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2993   FILE *file;
2994
2995   /* ----------------------------------------------------------------------- */
2996   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2997   /* ----------------------------------------------------------------------- */
2998
2999   InitLevelSetupDirectory(level_subdir);
3000
3001   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3002
3003   if (!(file = fopen(filename, MODE_WRITE)))
3004   {
3005     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3006     free(filename);
3007     return;
3008   }
3009
3010   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3011                                                  getCookie("LEVELSETUP")));
3012   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3013                                                level_nr_str));
3014   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3015                                                handicap_level_str));
3016
3017   fclose(file);
3018
3019   SetFilePermissions(filename, PERMS_PRIVATE);
3020
3021   free(filename);
3022 }