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