rnd-20050202-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->user_defined ? 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->user_defined);
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
1597 #define NUM_LEVELINFO_TOKENS            18
1598
1599 static LevelDirTree ldi;
1600
1601 static struct TokenInfo levelinfo_tokens[] =
1602 {
1603   /* level directory info */
1604   { TYPE_STRING,        &ldi.identifier,        "identifier"    },
1605   { TYPE_STRING,        &ldi.name,              "name"          },
1606   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"  },
1607   { TYPE_STRING,        &ldi.author,            "author"        },
1608   { TYPE_STRING,        &ldi.imported_from,     "imported_from" },
1609   { TYPE_STRING,        &ldi.imported_by,       "imported_by"   },
1610   { TYPE_INTEGER,       &ldi.levels,            "levels"        },
1611   { TYPE_INTEGER,       &ldi.first_level,       "first_level"   },
1612   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority" },
1613   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine" },
1614   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"   },
1615   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"      },
1616   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"  },
1617   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"    },
1618   { TYPE_STRING,        &ldi.music_set,         "music_set"     },
1619   { TYPE_STRING,        &ldi.level_filename,    "filename"      },
1620   { TYPE_STRING,        &ldi.level_filetype,    "filetype"      },
1621   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"      }
1622 };
1623
1624 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1625 {
1626   ldi->type = type;
1627
1628   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1629                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1630                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1631                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1632                    NULL);
1633
1634   ldi->node_parent = NULL;
1635   ldi->node_group = NULL;
1636   ldi->next = NULL;
1637
1638   ldi->cl_first = -1;
1639   ldi->cl_cursor = -1;
1640
1641   ldi->subdir = NULL;
1642   ldi->fullpath = NULL;
1643   ldi->basepath = NULL;
1644   ldi->identifier = NULL;
1645   ldi->name = getStringCopy(ANONYMOUS_NAME);
1646   ldi->name_sorting = NULL;
1647   ldi->author = getStringCopy(ANONYMOUS_NAME);
1648
1649   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1650   ldi->latest_engine = FALSE;                   /* default: get from level */
1651   ldi->parent_link = FALSE;
1652   ldi->user_defined = FALSE;
1653   ldi->color = 0;
1654   ldi->class_desc = NULL;
1655
1656   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1657   {
1658     ldi->imported_from = NULL;
1659     ldi->imported_by = NULL;
1660
1661     ldi->graphics_set = NULL;
1662     ldi->sounds_set = NULL;
1663     ldi->music_set = NULL;
1664     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1665     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1666     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1667
1668     ldi->level_filename = NULL;
1669     ldi->level_filetype = NULL;
1670
1671     ldi->levels = 0;
1672     ldi->first_level = 0;
1673     ldi->last_level = 0;
1674     ldi->level_group = FALSE;
1675     ldi->handicap_level = 0;
1676     ldi->readonly = TRUE;
1677     ldi->handicap = TRUE;
1678   }
1679 }
1680
1681 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1682 {
1683   if (parent == NULL)
1684   {
1685     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1686
1687     setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
1688
1689     return;
1690   }
1691
1692 #if 1
1693   /* copy all values from the parent structure */
1694
1695   ldi->type = parent->type;
1696
1697   ldi->node_top = parent->node_top;
1698   ldi->node_parent = parent;
1699   ldi->node_group = NULL;
1700   ldi->next = NULL;
1701
1702   ldi->cl_first = -1;
1703   ldi->cl_cursor = -1;
1704
1705   ldi->subdir = NULL;
1706   ldi->fullpath = NULL;
1707   ldi->basepath = NULL;
1708   ldi->identifier = NULL;
1709   ldi->name = getStringCopy(ANONYMOUS_NAME);
1710   ldi->name_sorting = NULL;
1711   ldi->author = getStringCopy(parent->author);
1712
1713   ldi->sort_priority = parent->sort_priority;
1714   ldi->latest_engine = parent->latest_engine;
1715   ldi->parent_link = FALSE;
1716   ldi->user_defined = parent->user_defined;
1717   ldi->color = parent->color;
1718   ldi->class_desc = getStringCopy(parent->class_desc);
1719
1720   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1721   {
1722     ldi->imported_from = getStringCopy(parent->imported_from);
1723     ldi->imported_by = getStringCopy(parent->imported_by);
1724
1725     ldi->graphics_set = NULL;
1726     ldi->sounds_set = NULL;
1727     ldi->music_set = NULL;
1728     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1729     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1730     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1731
1732     ldi->level_filename = NULL;
1733     ldi->level_filetype = NULL;
1734
1735     ldi->levels = 0;
1736     ldi->first_level = 0;
1737     ldi->last_level = 0;
1738     ldi->level_group = FALSE;
1739     ldi->handicap_level = 0;
1740     ldi->readonly = TRUE;
1741     ldi->handicap = TRUE;
1742   }
1743
1744 #else
1745
1746   /* first copy all values from the parent structure ... */
1747   *ldi = *parent;
1748
1749   /* ... then set all fields to default that cannot be inherited from parent.
1750      This is especially important for all those fields that can be set from
1751      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1752      calls 'free()' for all already set token values which requires that no
1753      other structure's pointer may point to them!
1754   */
1755
1756   ldi->subdir = NULL;
1757   ldi->fullpath = NULL;
1758   ldi->basepath = NULL;
1759   ldi->identifier = NULL;
1760   ldi->name = getStringCopy(ANONYMOUS_NAME);
1761   ldi->name_sorting = NULL;
1762   ldi->author = getStringCopy(parent->author);
1763
1764   ldi->imported_from = getStringCopy(parent->imported_from);
1765   ldi->imported_by = getStringCopy(parent->imported_by);
1766   ldi->class_desc = getStringCopy(parent->class_desc);
1767
1768   ldi->graphics_set = NULL;
1769   ldi->sounds_set = NULL;
1770   ldi->music_set = NULL;
1771   ldi->graphics_path = NULL;
1772   ldi->sounds_path = NULL;
1773   ldi->music_path = NULL;
1774
1775   ldi->level_group = FALSE;
1776   ldi->parent_link = FALSE;
1777
1778   ldi->node_top = parent->node_top;
1779   ldi->node_parent = parent;
1780   ldi->node_group = NULL;
1781   ldi->next = NULL;
1782
1783 #endif
1784 }
1785
1786 static void freeTreeInfo(TreeInfo *ldi)
1787 {
1788   checked_free(ldi->subdir);
1789   checked_free(ldi->fullpath);
1790   checked_free(ldi->basepath);
1791   checked_free(ldi->identifier);
1792
1793   checked_free(ldi->name);
1794   checked_free(ldi->name_sorting);
1795   checked_free(ldi->author);
1796
1797   checked_free(ldi->class_desc);
1798
1799   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1800   {
1801     checked_free(ldi->imported_from);
1802     checked_free(ldi->imported_by);
1803
1804     checked_free(ldi->graphics_set);
1805     checked_free(ldi->sounds_set);
1806     checked_free(ldi->music_set);
1807
1808     checked_free(ldi->graphics_path);
1809     checked_free(ldi->sounds_path);
1810     checked_free(ldi->music_path);
1811
1812     checked_free(ldi->level_filename);
1813     checked_free(ldi->level_filetype);
1814   }
1815 }
1816
1817 void setSetupInfo(struct TokenInfo *token_info,
1818                   int token_nr, char *token_value)
1819 {
1820   int token_type = token_info[token_nr].type;
1821   void *setup_value = token_info[token_nr].value;
1822
1823   if (token_value == NULL)
1824     return;
1825
1826   /* set setup field to corresponding token value */
1827   switch (token_type)
1828   {
1829     case TYPE_BOOLEAN:
1830     case TYPE_SWITCH:
1831       *(boolean *)setup_value = get_boolean_from_string(token_value);
1832       break;
1833
1834     case TYPE_KEY:
1835       *(Key *)setup_value = getKeyFromKeyName(token_value);
1836       break;
1837
1838     case TYPE_KEY_X11:
1839       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1840       break;
1841
1842     case TYPE_INTEGER:
1843       *(int *)setup_value = get_integer_from_string(token_value);
1844       break;
1845
1846     case TYPE_STRING:
1847       checked_free(*(char **)setup_value);
1848       *(char **)setup_value = getStringCopy(token_value);
1849       break;
1850
1851     default:
1852       break;
1853   }
1854 }
1855
1856 static int compareTreeInfoEntries(const void *object1, const void *object2)
1857 {
1858   const TreeInfo *entry1 = *((TreeInfo **)object1);
1859   const TreeInfo *entry2 = *((TreeInfo **)object2);
1860   int class_sorting1, class_sorting2;
1861   int compare_result;
1862
1863   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1864   {
1865     class_sorting1 = LEVELSORTING(entry1);
1866     class_sorting2 = LEVELSORTING(entry2);
1867   }
1868   else
1869   {
1870     class_sorting1 = ARTWORKSORTING(entry1);
1871     class_sorting2 = ARTWORKSORTING(entry2);
1872   }
1873
1874   if (entry1->parent_link || entry2->parent_link)
1875     compare_result = (entry1->parent_link ? -1 : +1);
1876   else if (entry1->sort_priority == entry2->sort_priority)
1877   {
1878     char *name1 = getStringToLower(entry1->name_sorting);
1879     char *name2 = getStringToLower(entry2->name_sorting);
1880
1881     compare_result = strcmp(name1, name2);
1882
1883     free(name1);
1884     free(name2);
1885   }
1886   else if (class_sorting1 == class_sorting2)
1887     compare_result = entry1->sort_priority - entry2->sort_priority;
1888   else
1889     compare_result = class_sorting1 - class_sorting2;
1890
1891   return compare_result;
1892 }
1893
1894 static void createParentTreeInfoNode(TreeInfo *node_parent)
1895 {
1896   TreeInfo *ti_new;
1897
1898   if (node_parent == NULL)
1899     return;
1900
1901   ti_new = newTreeInfo();
1902   setTreeInfoToDefaults(ti_new, node_parent->type);
1903
1904   ti_new->node_parent = node_parent;
1905   ti_new->parent_link = TRUE;
1906
1907 #if 1
1908   setString(&ti_new->identifier, node_parent->identifier);
1909   setString(&ti_new->name, ".. (parent directory)");
1910   setString(&ti_new->name_sorting, ti_new->name);
1911
1912   setString(&ti_new->subdir, "..");
1913   setString(&ti_new->fullpath, node_parent->fullpath);
1914
1915   ti_new->sort_priority = node_parent->sort_priority;
1916   ti_new->latest_engine = node_parent->latest_engine;
1917
1918   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
1919 #else
1920   ti_new->identifier = getStringCopy(node_parent->identifier);
1921   ti_new->name = ".. (parent directory)";
1922   ti_new->name_sorting = getStringCopy(ti_new->name);
1923
1924   ti_new->subdir = "..";
1925   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1926
1927   ti_new->sort_priority = node_parent->sort_priority;
1928   ti_new->latest_engine = node_parent->latest_engine;
1929
1930   ti_new->class_desc = getLevelClassDescription(ti_new);
1931 #endif
1932
1933   pushTreeInfo(&node_parent->node_group, ti_new);
1934 }
1935
1936 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1937 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1938
1939 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1940                                           TreeInfo *node_parent,
1941                                           char *level_directory,
1942                                           char *directory_name)
1943 {
1944   char *directory_path = getPath2(level_directory, directory_name);
1945   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1946   SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1947   LevelDirTree *leveldir_new = NULL;
1948   int i;
1949
1950   if (setup_file_hash == NULL)
1951   {
1952     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1953
1954     free(directory_path);
1955     free(filename);
1956
1957     return FALSE;
1958   }
1959
1960   leveldir_new = newTreeInfo();
1961
1962   if (node_parent)
1963     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1964   else
1965     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1966
1967   leveldir_new->subdir = getStringCopy(directory_name);
1968
1969   checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1970
1971   /* set all structure fields according to the token/value pairs */
1972   ldi = *leveldir_new;
1973   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
1974     setSetupInfo(levelinfo_tokens, i,
1975                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1976   *leveldir_new = ldi;
1977
1978 #if 1
1979   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1980     setString(&leveldir_new->name, leveldir_new->subdir);
1981 #else
1982   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1983   {
1984     free(leveldir_new->name);
1985     leveldir_new->name = getStringCopy(leveldir_new->subdir);
1986   }
1987 #endif
1988
1989   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1990
1991   if (leveldir_new->identifier == NULL)
1992     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
1993
1994   if (leveldir_new->name_sorting == NULL)
1995     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1996
1997   if (node_parent == NULL)              /* top level group */
1998   {
1999     leveldir_new->basepath = getStringCopy(level_directory);
2000     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2001   }
2002   else                                  /* sub level group */
2003   {
2004     leveldir_new->basepath = getStringCopy(node_parent->basepath);
2005     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2006   }
2007
2008   if (leveldir_new->levels < 1)
2009     leveldir_new->levels = 1;
2010
2011   leveldir_new->last_level =
2012     leveldir_new->first_level + leveldir_new->levels - 1;
2013
2014 #if 1
2015   leveldir_new->user_defined =
2016     (strcmp(leveldir_new->basepath, options.level_directory) != 0);
2017 #else
2018   leveldir_new->user_defined =
2019     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
2020 #endif
2021
2022   leveldir_new->color = LEVELCOLOR(leveldir_new);
2023 #if 1
2024   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2025 #else
2026   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
2027 #endif
2028
2029   leveldir_new->handicap_level =        /* set handicap to default value */
2030     (leveldir_new->user_defined || !leveldir_new->handicap ?
2031      leveldir_new->last_level : leveldir_new->first_level);
2032
2033   pushTreeInfo(node_first, leveldir_new);
2034
2035   freeSetupFileHash(setup_file_hash);
2036
2037   if (leveldir_new->level_group)
2038   {
2039     /* create node to link back to current level directory */
2040     createParentTreeInfoNode(leveldir_new);
2041
2042     /* step into sub-directory and look for more level series */
2043     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2044                               leveldir_new, directory_path);
2045   }
2046
2047   free(directory_path);
2048   free(filename);
2049
2050   return TRUE;
2051 }
2052
2053 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2054                                       TreeInfo *node_parent,
2055                                       char *level_directory)
2056 {
2057   DIR *dir;
2058   struct dirent *dir_entry;
2059   boolean valid_entry_found = FALSE;
2060
2061   if ((dir = opendir(level_directory)) == NULL)
2062   {
2063     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2064     return;
2065   }
2066
2067   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2068   {
2069     struct stat file_status;
2070     char *directory_name = dir_entry->d_name;
2071     char *directory_path = getPath2(level_directory, directory_name);
2072
2073     /* skip entries for current and parent directory */
2074     if (strcmp(directory_name, ".")  == 0 ||
2075         strcmp(directory_name, "..") == 0)
2076     {
2077       free(directory_path);
2078       continue;
2079     }
2080
2081     /* find out if directory entry is itself a directory */
2082     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2083         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2084     {
2085       free(directory_path);
2086       continue;
2087     }
2088
2089     free(directory_path);
2090
2091     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
2092         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
2093         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
2094       continue;
2095
2096     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2097                                                     level_directory,
2098                                                     directory_name);
2099   }
2100
2101   closedir(dir);
2102
2103   if (!valid_entry_found)
2104   {
2105     /* check if this directory directly contains a file "levelinfo.conf" */
2106     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2107                                                     level_directory, ".");
2108   }
2109
2110   if (!valid_entry_found)
2111     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2112           level_directory);
2113 }
2114
2115 void LoadLevelInfo()
2116 {
2117   InitUserLevelDirectory(getLoginName());
2118
2119   DrawInitText("Loading level series:", 120, FC_GREEN);
2120
2121   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2122   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2123
2124   /* before sorting, the first entries will be from the user directory */
2125   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2126
2127   if (leveldir_first == NULL)
2128     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2129
2130   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
2131
2132 #if 0
2133   dumpTreeInfo(leveldir_first, 0);
2134 #endif
2135 }
2136
2137 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2138                                               TreeInfo *node_parent,
2139                                               char *base_directory,
2140                                               char *directory_name, int type)
2141 {
2142   char *directory_path = getPath2(base_directory, directory_name);
2143   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2144   SetupFileHash *setup_file_hash = NULL;
2145   TreeInfo *artwork_new = NULL;
2146   int i;
2147
2148   if (access(filename, F_OK) == 0)              /* file exists */
2149     setup_file_hash = loadSetupFileHash(filename);
2150
2151   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2152   {
2153     DIR *dir;
2154     struct dirent *dir_entry;
2155     boolean valid_file_found = FALSE;
2156
2157     if ((dir = opendir(directory_path)) != NULL)
2158     {
2159       while ((dir_entry = readdir(dir)) != NULL)
2160       {
2161         char *entry_name = dir_entry->d_name;
2162
2163         if (FileIsArtworkType(entry_name, type))
2164         {
2165           valid_file_found = TRUE;
2166           break;
2167         }
2168       }
2169
2170       closedir(dir);
2171     }
2172
2173     if (!valid_file_found)
2174     {
2175       if (strcmp(directory_name, ".") != 0)
2176         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2177
2178       free(directory_path);
2179       free(filename);
2180
2181       return FALSE;
2182     }
2183   }
2184
2185   artwork_new = newTreeInfo();
2186
2187   if (node_parent)
2188     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2189   else
2190     setTreeInfoToDefaults(artwork_new, type);
2191
2192   artwork_new->subdir = getStringCopy(directory_name);
2193
2194   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2195   {
2196 #if 0
2197     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
2198 #endif
2199
2200     /* set all structure fields according to the token/value pairs */
2201     ldi = *artwork_new;
2202     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2203       setSetupInfo(levelinfo_tokens, i,
2204                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2205     *artwork_new = ldi;
2206
2207 #if 1
2208     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2209       setString(&artwork_new->name, artwork_new->subdir);
2210 #else
2211     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2212     {
2213       free(artwork_new->name);
2214       artwork_new->name = getStringCopy(artwork_new->subdir);
2215     }
2216 #endif
2217
2218 #if 0
2219     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2220 #endif
2221
2222     if (artwork_new->identifier == NULL)
2223       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2224
2225     if (artwork_new->name_sorting == NULL)
2226       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2227   }
2228
2229   if (node_parent == NULL)              /* top level group */
2230   {
2231     artwork_new->basepath = getStringCopy(base_directory);
2232     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2233   }
2234   else                                  /* sub level group */
2235   {
2236     artwork_new->basepath = getStringCopy(node_parent->basepath);
2237     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2238   }
2239
2240 #if 1
2241   artwork_new->user_defined =
2242     (strcmp(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)) != 0);
2243 #else
2244   artwork_new->user_defined =
2245     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
2246 #endif
2247
2248   /* (may use ".sort_priority" from "setup_file_hash" above) */
2249   artwork_new->color = ARTWORKCOLOR(artwork_new);
2250 #if 1
2251   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2252 #else
2253   artwork_new->class_desc = getLevelClassDescription(artwork_new);
2254 #endif
2255
2256   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2257   {
2258 #if 0
2259     if (artwork_new->name != NULL)
2260     {
2261       free(artwork_new->name);
2262       artwork_new->name = NULL;
2263     }
2264 #endif
2265
2266 #if 0
2267     if (artwork_new->identifier != NULL)
2268     {
2269       free(artwork_new->identifier);
2270       artwork_new->identifier = NULL;
2271     }
2272 #endif
2273
2274     if (strcmp(artwork_new->subdir, ".") == 0)
2275     {
2276       if (artwork_new->user_defined)
2277       {
2278 #if 1
2279         setString(&artwork_new->identifier, "private");
2280 #else
2281         artwork_new->identifier = getStringCopy("private");
2282 #endif
2283         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2284       }
2285       else
2286       {
2287 #if 1
2288         setString(&artwork_new->identifier, "classic");
2289 #else
2290         artwork_new->identifier = getStringCopy("classic");
2291 #endif
2292         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2293       }
2294
2295       /* set to new values after changing ".sort_priority" */
2296       artwork_new->color = ARTWORKCOLOR(artwork_new);
2297 #if 1
2298       setString(&artwork_new->class_desc,
2299                 getLevelClassDescription(artwork_new));
2300 #else
2301       artwork_new->class_desc = getLevelClassDescription(artwork_new);
2302 #endif
2303     }
2304     else
2305     {
2306 #if 1
2307       setString(&artwork_new->identifier, artwork_new->subdir);
2308 #else
2309       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2310 #endif
2311     }
2312
2313 #if 1
2314     setString(&artwork_new->name, artwork_new->identifier);
2315     setString(&artwork_new->name_sorting, artwork_new->name);
2316 #else
2317     artwork_new->name = getStringCopy(artwork_new->identifier);
2318     artwork_new->name_sorting = getStringCopy(artwork_new->name);
2319 #endif
2320   }
2321
2322   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2323
2324   pushTreeInfo(node_first, artwork_new);
2325
2326   freeSetupFileHash(setup_file_hash);
2327
2328   free(directory_path);
2329   free(filename);
2330
2331   return TRUE;
2332 }
2333
2334 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2335                                           TreeInfo *node_parent,
2336                                           char *base_directory, int type)
2337 {
2338   DIR *dir;
2339   struct dirent *dir_entry;
2340   boolean valid_entry_found = FALSE;
2341
2342   if ((dir = opendir(base_directory)) == NULL)
2343   {
2344     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2345       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2346     return;
2347   }
2348
2349   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2350   {
2351     struct stat file_status;
2352     char *directory_name = dir_entry->d_name;
2353     char *directory_path = getPath2(base_directory, directory_name);
2354
2355     /* skip entries for current and parent directory */
2356     if (strcmp(directory_name, ".")  == 0 ||
2357         strcmp(directory_name, "..") == 0)
2358     {
2359       free(directory_path);
2360       continue;
2361     }
2362
2363     /* find out if directory entry is itself a directory */
2364     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2365         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2366     {
2367       free(directory_path);
2368       continue;
2369     }
2370
2371     free(directory_path);
2372
2373     /* check if this directory contains artwork with or without config file */
2374     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2375                                                         base_directory,
2376                                                         directory_name, type);
2377   }
2378
2379   closedir(dir);
2380
2381   /* check if this directory directly contains artwork itself */
2382   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2383                                                       base_directory, ".",
2384                                                       type);
2385   if (!valid_entry_found)
2386     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2387           base_directory);
2388 }
2389
2390 static TreeInfo *getDummyArtworkInfo(int type)
2391 {
2392   /* this is only needed when there is completely no artwork available */
2393   TreeInfo *artwork_new = newTreeInfo();
2394
2395   setTreeInfoToDefaults(artwork_new, type);
2396
2397 #if 1
2398   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
2399   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2400   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2401
2402   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2403   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2404   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2405 #else
2406   artwork_new->subdir   = getStringCopy(UNDEFINED_FILENAME);
2407   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2408   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2409
2410   checked_free(artwork_new->name);
2411
2412   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
2413   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
2414   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2415 #endif
2416
2417   return artwork_new;
2418 }
2419
2420 void LoadArtworkInfo()
2421 {
2422   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2423
2424   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2425                                 options.graphics_directory,
2426                                 TREE_TYPE_GRAPHICS_DIR);
2427   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2428                                 getUserGraphicsDir(),
2429                                 TREE_TYPE_GRAPHICS_DIR);
2430
2431   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2432                                 options.sounds_directory,
2433                                 TREE_TYPE_SOUNDS_DIR);
2434   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2435                                 getUserSoundsDir(),
2436                                 TREE_TYPE_SOUNDS_DIR);
2437
2438   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2439                                 options.music_directory,
2440                                 TREE_TYPE_MUSIC_DIR);
2441   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2442                                 getUserMusicDir(),
2443                                 TREE_TYPE_MUSIC_DIR);
2444
2445   if (artwork.gfx_first == NULL)
2446     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2447   if (artwork.snd_first == NULL)
2448     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2449   if (artwork.mus_first == NULL)
2450     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2451
2452   /* before sorting, the first entries will be from the user directory */
2453   artwork.gfx_current =
2454     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2455   if (artwork.gfx_current == NULL)
2456     artwork.gfx_current =
2457       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2458   if (artwork.gfx_current == NULL)
2459     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2460
2461   artwork.snd_current =
2462     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2463   if (artwork.snd_current == NULL)
2464     artwork.snd_current =
2465       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2466   if (artwork.snd_current == NULL)
2467     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2468
2469   artwork.mus_current =
2470     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2471   if (artwork.mus_current == NULL)
2472     artwork.mus_current =
2473       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2474   if (artwork.mus_current == NULL)
2475     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2476
2477   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2478   artwork.snd_current_identifier = artwork.snd_current->identifier;
2479   artwork.mus_current_identifier = artwork.mus_current->identifier;
2480
2481 #if 0
2482   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2483   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2484   printf("music set == %s\n\n", artwork.mus_current_identifier);
2485 #endif
2486
2487   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2488   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2489   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2490
2491 #if 0
2492   dumpTreeInfo(artwork.gfx_first, 0);
2493   dumpTreeInfo(artwork.snd_first, 0);
2494   dumpTreeInfo(artwork.mus_first, 0);
2495 #endif
2496 }
2497
2498 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2499                                   LevelDirTree *level_node)
2500 {
2501   /* recursively check all level directories for artwork sub-directories */
2502
2503   while (level_node)
2504   {
2505     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2506                           ARTWORK_DIRECTORY((*artwork_node)->type));
2507
2508 #if 0
2509     if (!level_node->parent_link)
2510       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2511              level_node->subdir, level_node->name);
2512 #endif
2513
2514     if (!level_node->parent_link)
2515     {
2516       TreeInfo *topnode_last = *artwork_node;
2517
2518       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2519                                     (*artwork_node)->type);
2520
2521       if (topnode_last != *artwork_node)
2522       {
2523         free((*artwork_node)->identifier);
2524         free((*artwork_node)->name);
2525         free((*artwork_node)->name_sorting);
2526
2527         (*artwork_node)->identifier   = getStringCopy(level_node->subdir);
2528         (*artwork_node)->name         = getStringCopy(level_node->name);
2529         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2530
2531         (*artwork_node)->sort_priority = level_node->sort_priority;
2532         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2533       }
2534     }
2535
2536     free(path);
2537
2538     if (level_node->node_group != NULL)
2539       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2540
2541     level_node = level_node->next;
2542   }
2543 }
2544
2545 void LoadLevelArtworkInfo()
2546 {
2547   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2548
2549   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2550   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2551   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2552
2553   /* needed for reloading level artwork not known at ealier stage */
2554
2555   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2556   {
2557     artwork.gfx_current =
2558       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2559     if (artwork.gfx_current == NULL)
2560       artwork.gfx_current =
2561         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2562     if (artwork.gfx_current == NULL)
2563       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2564   }
2565
2566   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2567   {
2568     artwork.snd_current =
2569       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2570     if (artwork.snd_current == NULL)
2571       artwork.snd_current =
2572         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2573     if (artwork.snd_current == NULL)
2574       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2575   }
2576
2577   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2578   {
2579     artwork.mus_current =
2580       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2581     if (artwork.mus_current == NULL)
2582       artwork.mus_current =
2583         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2584     if (artwork.mus_current == NULL)
2585       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2586   }
2587
2588   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2589   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2590   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2591
2592 #if 0
2593   dumpTreeInfo(artwork.gfx_first, 0);
2594   dumpTreeInfo(artwork.snd_first, 0);
2595   dumpTreeInfo(artwork.mus_first, 0);
2596 #endif
2597 }
2598
2599 static void SaveUserLevelInfo()
2600 {
2601   LevelDirTree *level_info;
2602   char *filename;
2603   FILE *file;
2604   int i;
2605
2606   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2607
2608   if (!(file = fopen(filename, MODE_WRITE)))
2609   {
2610     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2611     free(filename);
2612     return;
2613   }
2614
2615   level_info = newTreeInfo();
2616
2617   /* always start with reliable default values */
2618   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2619
2620 #if 1
2621   setString(&level_info->name, getLoginName());
2622   setString(&level_info->author, getRealName());
2623   level_info->levels = 100;
2624   level_info->first_level = 1;
2625   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
2626   level_info->readonly = FALSE;
2627   setString(&level_info->graphics_set, GFX_CLASSIC_SUBDIR);
2628   setString(&level_info->sounds_set,   SND_CLASSIC_SUBDIR);
2629   setString(&level_info->music_set,    MUS_CLASSIC_SUBDIR);
2630 #else
2631   ldi.name = getStringCopy(getLoginName());
2632   ldi.author = getStringCopy(getRealName());
2633   ldi.levels = 100;
2634   ldi.first_level = 1;
2635   ldi.sort_priority = LEVELCLASS_PRIVATE_START;
2636   ldi.readonly = FALSE;
2637   ldi.graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
2638   ldi.sounds_set = getStringCopy(SND_CLASSIC_SUBDIR);
2639   ldi.music_set = getStringCopy(MUS_CLASSIC_SUBDIR);
2640 #endif
2641
2642   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2643                                                  getCookie("LEVELINFO")));
2644
2645   ldi = *level_info;
2646   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2647     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2648         i != LEVELINFO_TOKEN_NAME_SORTING &&
2649         i != LEVELINFO_TOKEN_IMPORTED_FROM &&
2650         i != LEVELINFO_TOKEN_IMPORTED_BY &&
2651         i != LEVELINFO_TOKEN_FILENAME &&
2652         i != LEVELINFO_TOKEN_FILETYPE)
2653       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2654
2655   fclose(file);
2656
2657   SetFilePermissions(filename, PERMS_PRIVATE);
2658
2659   freeTreeInfo(level_info);
2660   free(filename);
2661 }
2662
2663 char *getSetupValue(int type, void *value)
2664 {
2665   static char value_string[MAX_LINE_LEN];
2666
2667   if (value == NULL)
2668     return NULL;
2669
2670   switch (type)
2671   {
2672     case TYPE_BOOLEAN:
2673       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2674       break;
2675
2676     case TYPE_SWITCH:
2677       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2678       break;
2679
2680     case TYPE_YES_NO:
2681       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2682       break;
2683
2684     case TYPE_KEY:
2685       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2686       break;
2687
2688     case TYPE_KEY_X11:
2689       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2690       break;
2691
2692     case TYPE_INTEGER:
2693       sprintf(value_string, "%d", *(int *)value);
2694       break;
2695
2696     case TYPE_STRING:
2697       strcpy(value_string, *(char **)value);
2698       break;
2699
2700     default:
2701       value_string[0] = '\0';
2702       break;
2703   }
2704
2705   return value_string;
2706 }
2707
2708 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2709 {
2710   int i;
2711   char *line;
2712   static char token_string[MAX_LINE_LEN];
2713   int token_type = token_info[token_nr].type;
2714   void *setup_value = token_info[token_nr].value;
2715   char *token_text = token_info[token_nr].text;
2716   char *value_string = getSetupValue(token_type, setup_value);
2717
2718   /* build complete token string */
2719   sprintf(token_string, "%s%s", prefix, token_text);
2720
2721   /* build setup entry line */
2722   line = getFormattedSetupEntry(token_string, value_string);
2723
2724   if (token_type == TYPE_KEY_X11)
2725   {
2726     Key key = *(Key *)setup_value;
2727     char *keyname = getKeyNameFromKey(key);
2728
2729     /* add comment, if useful */
2730     if (strcmp(keyname, "(undefined)") != 0 &&
2731         strcmp(keyname, "(unknown)") != 0)
2732     {
2733       /* add at least one whitespace */
2734       strcat(line, " ");
2735       for (i = strlen(line); i < TOKEN_COMMENT_POSITION; i++)
2736         strcat(line, " ");
2737
2738       strcat(line, "# ");
2739       strcat(line, keyname);
2740     }
2741   }
2742
2743   return line;
2744 }
2745
2746 void LoadLevelSetup_LastSeries()
2747 {
2748   /* ----------------------------------------------------------------------- */
2749   /* ~/.<program>/levelsetup.conf                                            */
2750   /* ----------------------------------------------------------------------- */
2751
2752   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2753   SetupFileHash *level_setup_hash = NULL;
2754
2755   /* always start with reliable default values */
2756   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2757
2758   if ((level_setup_hash = loadSetupFileHash(filename)))
2759   {
2760     char *last_level_series =
2761       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2762
2763     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2764                                                  last_level_series);
2765     if (leveldir_current == NULL)
2766       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2767
2768     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2769
2770     freeSetupFileHash(level_setup_hash);
2771   }
2772   else
2773     Error(ERR_WARN, "using default setup values");
2774
2775   free(filename);
2776 }
2777
2778 void SaveLevelSetup_LastSeries()
2779 {
2780   /* ----------------------------------------------------------------------- */
2781   /* ~/.<program>/levelsetup.conf                                            */
2782   /* ----------------------------------------------------------------------- */
2783
2784   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2785   char *level_subdir = leveldir_current->subdir;
2786   FILE *file;
2787
2788   InitUserDataDirectory();
2789
2790   if (!(file = fopen(filename, MODE_WRITE)))
2791   {
2792     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2793     free(filename);
2794     return;
2795   }
2796
2797   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2798                                                  getCookie("LEVELSETUP")));
2799   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2800                                                level_subdir));
2801
2802   fclose(file);
2803
2804   SetFilePermissions(filename, PERMS_PRIVATE);
2805
2806   free(filename);
2807 }
2808
2809 static void checkSeriesInfo()
2810 {
2811   static char *level_directory = NULL;
2812   DIR *dir;
2813   struct dirent *dir_entry;
2814
2815   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2816
2817   level_directory = getPath2((leveldir_current->user_defined ?
2818                               getUserLevelDir(NULL) :
2819                               options.level_directory),
2820                              leveldir_current->fullpath);
2821
2822   if ((dir = opendir(level_directory)) == NULL)
2823   {
2824     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2825     return;
2826   }
2827
2828   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2829   {
2830     if (strlen(dir_entry->d_name) > 4 &&
2831         dir_entry->d_name[3] == '.' &&
2832         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2833     {
2834       char levelnum_str[4];
2835       int levelnum_value;
2836
2837       strncpy(levelnum_str, dir_entry->d_name, 3);
2838       levelnum_str[3] = '\0';
2839
2840       levelnum_value = atoi(levelnum_str);
2841
2842 #if 0
2843       if (levelnum_value < leveldir_current->first_level)
2844       {
2845         Error(ERR_WARN, "additional level %d found", levelnum_value);
2846         leveldir_current->first_level = levelnum_value;
2847       }
2848       else if (levelnum_value > leveldir_current->last_level)
2849       {
2850         Error(ERR_WARN, "additional level %d found", levelnum_value);
2851         leveldir_current->last_level = levelnum_value;
2852       }
2853 #endif
2854     }
2855   }
2856
2857   closedir(dir);
2858 }
2859
2860 void LoadLevelSetup_SeriesInfo()
2861 {
2862   char *filename;
2863   SetupFileHash *level_setup_hash = NULL;
2864   char *level_subdir = leveldir_current->subdir;
2865
2866   /* always start with reliable default values */
2867   level_nr = leveldir_current->first_level;
2868
2869   checkSeriesInfo(leveldir_current);
2870
2871   /* ----------------------------------------------------------------------- */
2872   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2873   /* ----------------------------------------------------------------------- */
2874
2875   level_subdir = leveldir_current->subdir;
2876
2877   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2878
2879   if ((level_setup_hash = loadSetupFileHash(filename)))
2880   {
2881     char *token_value;
2882
2883     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2884
2885     if (token_value)
2886     {
2887       level_nr = atoi(token_value);
2888
2889       if (level_nr < leveldir_current->first_level)
2890         level_nr = leveldir_current->first_level;
2891       if (level_nr > leveldir_current->last_level)
2892         level_nr = leveldir_current->last_level;
2893     }
2894
2895     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2896
2897     if (token_value)
2898     {
2899       int level_nr = atoi(token_value);
2900
2901       if (level_nr < leveldir_current->first_level)
2902         level_nr = leveldir_current->first_level;
2903       if (level_nr > leveldir_current->last_level + 1)
2904         level_nr = leveldir_current->last_level;
2905
2906       if (leveldir_current->user_defined || !leveldir_current->handicap)
2907         level_nr = leveldir_current->last_level;
2908
2909       leveldir_current->handicap_level = level_nr;
2910     }
2911
2912     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2913
2914     freeSetupFileHash(level_setup_hash);
2915   }
2916   else
2917     Error(ERR_WARN, "using default setup values");
2918
2919   free(filename);
2920 }
2921
2922 void SaveLevelSetup_SeriesInfo()
2923 {
2924   char *filename;
2925   char *level_subdir = leveldir_current->subdir;
2926   char *level_nr_str = int2str(level_nr, 0);
2927   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2928   FILE *file;
2929
2930   /* ----------------------------------------------------------------------- */
2931   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2932   /* ----------------------------------------------------------------------- */
2933
2934   InitLevelSetupDirectory(level_subdir);
2935
2936   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2937
2938   if (!(file = fopen(filename, MODE_WRITE)))
2939   {
2940     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2941     free(filename);
2942     return;
2943   }
2944
2945   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2946                                                  getCookie("LEVELSETUP")));
2947   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2948                                                level_nr_str));
2949   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2950                                                handicap_level_str));
2951
2952   fclose(file);
2953
2954   SetFilePermissions(filename, PERMS_PRIVATE);
2955
2956   free(filename);
2957 }