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