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