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