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