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