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