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