rnd-20030803-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
1501     ldi->graphics_set = NULL;
1502     ldi->sounds_set = NULL;
1503     ldi->music_set = NULL;
1504     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1505     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1506     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1507
1508     ldi->levels = 0;
1509     ldi->first_level = 0;
1510     ldi->last_level = 0;
1511     ldi->level_group = FALSE;
1512     ldi->handicap_level = 0;
1513     ldi->readonly = TRUE;
1514   }
1515 }
1516
1517 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1518 {
1519   if (parent == NULL)
1520   {
1521     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1522
1523     setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
1524
1525     return;
1526   }
1527
1528 #if 1
1529   /* copy all values from the parent structure */
1530
1531   ldi->type = parent->type;
1532
1533   ldi->node_top = parent->node_top;
1534   ldi->node_parent = parent;
1535   ldi->node_group = NULL;
1536   ldi->next = NULL;
1537
1538   ldi->cl_first = -1;
1539   ldi->cl_cursor = -1;
1540
1541   ldi->filename = NULL;
1542   ldi->fullpath = NULL;
1543   ldi->basepath = NULL;
1544   ldi->identifier = NULL;
1545   ldi->name = getStringCopy(ANONYMOUS_NAME);
1546   ldi->name_sorting = NULL;
1547   ldi->author = getStringCopy(parent->author);
1548
1549   ldi->sort_priority = parent->sort_priority;
1550   ldi->parent_link = FALSE;
1551   ldi->user_defined = parent->user_defined;
1552   ldi->color = parent->color;
1553   ldi->class_desc = getStringCopy(parent->class_desc);
1554
1555   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1556   {
1557     ldi->imported_from = getStringCopy(parent->imported_from);
1558
1559     ldi->graphics_set = NULL;
1560     ldi->sounds_set = NULL;
1561     ldi->music_set = NULL;
1562     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1563     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1564     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1565
1566     ldi->levels = 0;
1567     ldi->first_level = 0;
1568     ldi->last_level = 0;
1569     ldi->level_group = FALSE;
1570     ldi->handicap_level = 0;
1571     ldi->readonly = TRUE;
1572   }
1573
1574
1575 #else
1576
1577   /* first copy all values from the parent structure ... */
1578   *ldi = *parent;
1579
1580   /* ... then set all fields to default that cannot be inherited from parent.
1581      This is especially important for all those fields that can be set from
1582      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1583      calls 'free()' for all already set token values which requires that no
1584      other structure's pointer may point to them!
1585   */
1586
1587   ldi->filename = NULL;
1588   ldi->fullpath = NULL;
1589   ldi->basepath = NULL;
1590   ldi->identifier = NULL;
1591   ldi->name = getStringCopy(ANONYMOUS_NAME);
1592   ldi->name_sorting = NULL;
1593   ldi->author = getStringCopy(parent->author);
1594
1595   ldi->imported_from = getStringCopy(parent->imported_from);
1596   ldi->class_desc = getStringCopy(parent->class_desc);
1597
1598   ldi->graphics_set = NULL;
1599   ldi->sounds_set = NULL;
1600   ldi->music_set = NULL;
1601   ldi->graphics_path = NULL;
1602   ldi->sounds_path = NULL;
1603   ldi->music_path = NULL;
1604
1605   ldi->level_group = FALSE;
1606   ldi->parent_link = FALSE;
1607
1608   ldi->node_top = parent->node_top;
1609   ldi->node_parent = parent;
1610   ldi->node_group = NULL;
1611   ldi->next = NULL;
1612
1613 #endif
1614 }
1615
1616 void setSetupInfo(struct TokenInfo *token_info,
1617                   int token_nr, char *token_value)
1618 {
1619   int token_type = token_info[token_nr].type;
1620   void *setup_value = token_info[token_nr].value;
1621
1622   if (token_value == NULL)
1623     return;
1624
1625   /* set setup field to corresponding token value */
1626   switch (token_type)
1627   {
1628     case TYPE_BOOLEAN:
1629     case TYPE_SWITCH:
1630       *(boolean *)setup_value = get_boolean_from_string(token_value);
1631       break;
1632
1633     case TYPE_KEY:
1634       *(Key *)setup_value = getKeyFromKeyName(token_value);
1635       break;
1636
1637     case TYPE_KEY_X11:
1638       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1639       break;
1640
1641     case TYPE_INTEGER:
1642       *(int *)setup_value = get_integer_from_string(token_value);
1643       break;
1644
1645     case TYPE_STRING:
1646       if (*(char **)setup_value != NULL)
1647         free(*(char **)setup_value);
1648       *(char **)setup_value = getStringCopy(token_value);
1649       break;
1650
1651     default:
1652       break;
1653   }
1654 }
1655
1656 static int compareTreeInfoEntries(const void *object1, const void *object2)
1657 {
1658   const TreeInfo *entry1 = *((TreeInfo **)object1);
1659   const TreeInfo *entry2 = *((TreeInfo **)object2);
1660   int class_sorting1, class_sorting2;
1661   int compare_result;
1662
1663   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1664   {
1665     class_sorting1 = LEVELSORTING(entry1);
1666     class_sorting2 = LEVELSORTING(entry2);
1667   }
1668   else
1669   {
1670     class_sorting1 = ARTWORKSORTING(entry1);
1671     class_sorting2 = ARTWORKSORTING(entry2);
1672   }
1673
1674   if (entry1->parent_link || entry2->parent_link)
1675     compare_result = (entry1->parent_link ? -1 : +1);
1676   else if (entry1->sort_priority == entry2->sort_priority)
1677   {
1678     char *name1 = getStringToLower(entry1->name_sorting);
1679     char *name2 = getStringToLower(entry2->name_sorting);
1680
1681     compare_result = strcmp(name1, name2);
1682
1683     free(name1);
1684     free(name2);
1685   }
1686   else if (class_sorting1 == class_sorting2)
1687     compare_result = entry1->sort_priority - entry2->sort_priority;
1688   else
1689     compare_result = class_sorting1 - class_sorting2;
1690
1691   return compare_result;
1692 }
1693
1694 static void createParentTreeInfoNode(TreeInfo *node_parent)
1695 {
1696   TreeInfo *ti_new;
1697
1698   if (node_parent == NULL)
1699     return;
1700
1701   ti_new = newTreeInfo();
1702   setTreeInfoToDefaults(ti_new, node_parent->type);
1703
1704   ti_new->node_parent = node_parent;
1705   ti_new->parent_link = TRUE;
1706
1707   ti_new->identifier = getStringCopy(node_parent->identifier);
1708   ti_new->name = ".. (parent directory)";
1709   ti_new->name_sorting = getStringCopy(ti_new->name);
1710
1711   ti_new->filename = "..";
1712   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1713
1714   ti_new->sort_priority = node_parent->sort_priority;
1715   ti_new->class_desc = getLevelClassDescription(ti_new);
1716
1717   pushTreeInfo(&node_parent->node_group, ti_new);
1718 }
1719
1720 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1721 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1722
1723 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1724                                           TreeInfo *node_parent,
1725                                           char *level_directory,
1726                                           char *directory_name)
1727 {
1728   char *directory_path = getPath2(level_directory, directory_name);
1729   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1730   SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1731   LevelDirTree *leveldir_new = NULL;
1732   int i;
1733
1734   if (setup_file_hash == NULL)
1735   {
1736     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1737
1738     free(directory_path);
1739     free(filename);
1740
1741     return FALSE;
1742   }
1743
1744   leveldir_new = newTreeInfo();
1745
1746   if (node_parent)
1747     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1748   else
1749     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1750
1751   leveldir_new->filename = getStringCopy(directory_name);
1752
1753   checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1754
1755   /* set all structure fields according to the token/value pairs */
1756   ldi = *leveldir_new;
1757   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1758     setSetupInfo(levelinfo_tokens, i,
1759                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1760   *leveldir_new = ldi;
1761
1762   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1763   {
1764     free(leveldir_new->name);
1765     leveldir_new->name = getStringCopy(leveldir_new->filename);
1766   }
1767
1768   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1769
1770   if (leveldir_new->identifier == NULL)
1771     leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1772
1773   if (leveldir_new->name_sorting == NULL)
1774     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1775
1776   if (node_parent == NULL)              /* top level group */
1777   {
1778     leveldir_new->basepath = level_directory;
1779     leveldir_new->fullpath = leveldir_new->filename;
1780   }
1781   else                                  /* sub level group */
1782   {
1783     leveldir_new->basepath = node_parent->basepath;
1784     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1785   }
1786
1787   if (leveldir_new->levels < 1)
1788     leveldir_new->levels = 1;
1789
1790   leveldir_new->last_level =
1791     leveldir_new->first_level + leveldir_new->levels - 1;
1792
1793   leveldir_new->user_defined =
1794     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1795
1796   leveldir_new->color = LEVELCOLOR(leveldir_new);
1797   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1798
1799   leveldir_new->handicap_level =        /* set handicap to default value */
1800     (leveldir_new->user_defined ?
1801      leveldir_new->last_level :
1802      leveldir_new->first_level);
1803
1804   pushTreeInfo(node_first, leveldir_new);
1805
1806   freeSetupFileHash(setup_file_hash);
1807
1808   if (leveldir_new->level_group)
1809   {
1810     /* create node to link back to current level directory */
1811     createParentTreeInfoNode(leveldir_new);
1812
1813     /* step into sub-directory and look for more level series */
1814     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1815                               leveldir_new, directory_path);
1816   }
1817
1818   free(directory_path);
1819   free(filename);
1820
1821   return TRUE;
1822 }
1823
1824 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1825                                       TreeInfo *node_parent,
1826                                       char *level_directory)
1827 {
1828   DIR *dir;
1829   struct dirent *dir_entry;
1830   boolean valid_entry_found = FALSE;
1831
1832   if ((dir = opendir(level_directory)) == NULL)
1833   {
1834     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1835     return;
1836   }
1837
1838   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1839   {
1840     struct stat file_status;
1841     char *directory_name = dir_entry->d_name;
1842     char *directory_path = getPath2(level_directory, directory_name);
1843
1844     /* skip entries for current and parent directory */
1845     if (strcmp(directory_name, ".")  == 0 ||
1846         strcmp(directory_name, "..") == 0)
1847     {
1848       free(directory_path);
1849       continue;
1850     }
1851
1852     /* find out if directory entry is itself a directory */
1853     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1854         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1855     {
1856       free(directory_path);
1857       continue;
1858     }
1859
1860     free(directory_path);
1861
1862     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
1863         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
1864         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
1865       continue;
1866
1867     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1868                                                     level_directory,
1869                                                     directory_name);
1870   }
1871
1872   closedir(dir);
1873
1874   if (!valid_entry_found)
1875   {
1876     /* check if this directory directly contains a file "levelinfo.conf" */
1877     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1878                                                     level_directory, ".");
1879   }
1880
1881   if (!valid_entry_found)
1882     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1883           level_directory);
1884 }
1885
1886 void LoadLevelInfo()
1887 {
1888   InitUserLevelDirectory(getLoginName());
1889
1890   DrawInitText("Loading level series:", 120, FC_GREEN);
1891
1892   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1893   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1894
1895   /* before sorting, the first entries will be from the user directory */
1896   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1897
1898   if (leveldir_first == NULL)
1899     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1900
1901   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1902
1903 #if 0
1904   dumpTreeInfo(leveldir_first, 0);
1905 #endif
1906 }
1907
1908 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1909                                               TreeInfo *node_parent,
1910                                               char *base_directory,
1911                                               char *directory_name, int type)
1912 {
1913   char *directory_path = getPath2(base_directory, directory_name);
1914   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
1915   SetupFileHash *setup_file_hash = NULL;
1916   TreeInfo *artwork_new = NULL;
1917   int i;
1918
1919   if (access(filename, F_OK) == 0)              /* file exists */
1920     setup_file_hash = loadSetupFileHash(filename);
1921
1922   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
1923   {
1924     DIR *dir;
1925     struct dirent *dir_entry;
1926     boolean valid_file_found = FALSE;
1927
1928     if ((dir = opendir(directory_path)) != NULL)
1929     {
1930       while ((dir_entry = readdir(dir)) != NULL)
1931       {
1932         char *entry_name = dir_entry->d_name;
1933
1934         if (FileIsArtworkType(entry_name, type))
1935         {
1936           valid_file_found = TRUE;
1937           break;
1938         }
1939       }
1940
1941       closedir(dir);
1942     }
1943
1944     if (!valid_file_found)
1945     {
1946       if (strcmp(directory_name, ".") != 0)
1947         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
1948
1949       free(directory_path);
1950       free(filename);
1951
1952       return FALSE;
1953     }
1954   }
1955
1956   artwork_new = newTreeInfo();
1957
1958   if (node_parent)
1959     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1960   else
1961     setTreeInfoToDefaults(artwork_new, type);
1962
1963   artwork_new->filename = getStringCopy(directory_name);
1964
1965   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
1966   {
1967 #if 0
1968     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
1969 #endif
1970
1971     /* set all structure fields according to the token/value pairs */
1972     ldi = *artwork_new;
1973     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1974       setSetupInfo(levelinfo_tokens, i,
1975                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1976     *artwork_new = ldi;
1977
1978     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
1979     {
1980       free(artwork_new->name);
1981       artwork_new->name = getStringCopy(artwork_new->filename);
1982     }
1983
1984 #if 0
1985     DrawInitText(artwork_new->name, 150, FC_YELLOW);
1986 #endif
1987
1988     if (artwork_new->identifier == NULL)
1989       artwork_new->identifier = getStringCopy(artwork_new->filename);
1990
1991     if (artwork_new->name_sorting == NULL)
1992       artwork_new->name_sorting = getStringCopy(artwork_new->name);
1993   }
1994
1995   if (node_parent == NULL)              /* top level group */
1996   {
1997     artwork_new->basepath = getStringCopy(base_directory);
1998     artwork_new->fullpath = getStringCopy(artwork_new->filename);
1999   }
2000   else                                  /* sub level group */
2001   {
2002     artwork_new->basepath = getStringCopy(node_parent->basepath);
2003     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2004   }
2005
2006   artwork_new->user_defined =
2007     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
2008
2009   /* (may use ".sort_priority" from "setup_file_hash" above) */
2010   artwork_new->color = ARTWORKCOLOR(artwork_new);
2011   artwork_new->class_desc = getLevelClassDescription(artwork_new);
2012
2013   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2014   {
2015     if (artwork_new->name != NULL)
2016       free(artwork_new->name);
2017
2018     if (strcmp(artwork_new->filename, ".") == 0)
2019     {
2020       if (artwork_new->user_defined)
2021       {
2022         artwork_new->identifier = getStringCopy("private");
2023         artwork_new->sort_priority = ARTWORKCLASS_USER;
2024       }
2025       else
2026       {
2027         artwork_new->identifier = getStringCopy("classic");
2028         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2029       }
2030
2031       /* set to new values after changing ".sort_priority" */
2032       artwork_new->color = ARTWORKCOLOR(artwork_new);
2033       artwork_new->class_desc = getLevelClassDescription(artwork_new);
2034     }
2035     else
2036     {
2037       artwork_new->identifier = getStringCopy(artwork_new->filename);
2038     }
2039
2040     artwork_new->name = getStringCopy(artwork_new->identifier);
2041     artwork_new->name_sorting = getStringCopy(artwork_new->name);
2042   }
2043
2044   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2045
2046   pushTreeInfo(node_first, artwork_new);
2047
2048   freeSetupFileHash(setup_file_hash);
2049
2050   free(directory_path);
2051   free(filename);
2052
2053   return TRUE;
2054 }
2055
2056 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2057                                           TreeInfo *node_parent,
2058                                           char *base_directory, int type)
2059 {
2060   DIR *dir;
2061   struct dirent *dir_entry;
2062   boolean valid_entry_found = FALSE;
2063
2064   if ((dir = opendir(base_directory)) == NULL)
2065   {
2066     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2067       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2068     return;
2069   }
2070
2071   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2072   {
2073     struct stat file_status;
2074     char *directory_name = dir_entry->d_name;
2075     char *directory_path = getPath2(base_directory, directory_name);
2076
2077     /* skip entries for current and parent directory */
2078     if (strcmp(directory_name, ".")  == 0 ||
2079         strcmp(directory_name, "..") == 0)
2080     {
2081       free(directory_path);
2082       continue;
2083     }
2084
2085     /* find out if directory entry is itself a directory */
2086     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2087         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2088     {
2089       free(directory_path);
2090       continue;
2091     }
2092
2093     free(directory_path);
2094
2095     /* check if this directory contains artwork with or without config file */
2096     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2097                                                         base_directory,
2098                                                         directory_name, type);
2099   }
2100
2101   closedir(dir);
2102
2103   /* check if this directory directly contains artwork itself */
2104   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2105                                                       base_directory, ".",
2106                                                       type);
2107   if (!valid_entry_found)
2108     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2109           base_directory);
2110 }
2111
2112 static TreeInfo *getDummyArtworkInfo(int type)
2113 {
2114   /* this is only needed when there is completely no artwork available */
2115   TreeInfo *artwork_new = newTreeInfo();
2116
2117   setTreeInfoToDefaults(artwork_new, type);
2118
2119   artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
2120   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2121   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2122
2123   if (artwork_new->name != NULL)
2124     free(artwork_new->name);
2125
2126   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
2127   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
2128   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2129
2130   return artwork_new;
2131 }
2132
2133 void LoadArtworkInfo()
2134 {
2135   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2136
2137   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2138                                 options.graphics_directory,
2139                                 TREE_TYPE_GRAPHICS_DIR);
2140   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2141                                 getUserGraphicsDir(),
2142                                 TREE_TYPE_GRAPHICS_DIR);
2143
2144   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2145                                 options.sounds_directory,
2146                                 TREE_TYPE_SOUNDS_DIR);
2147   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2148                                 getUserSoundsDir(),
2149                                 TREE_TYPE_SOUNDS_DIR);
2150
2151   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2152                                 options.music_directory,
2153                                 TREE_TYPE_MUSIC_DIR);
2154   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2155                                 getUserMusicDir(),
2156                                 TREE_TYPE_MUSIC_DIR);
2157
2158   if (artwork.gfx_first == NULL)
2159     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2160   if (artwork.snd_first == NULL)
2161     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2162   if (artwork.mus_first == NULL)
2163     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2164
2165   /* before sorting, the first entries will be from the user directory */
2166   artwork.gfx_current =
2167     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2168   if (artwork.gfx_current == NULL)
2169     artwork.gfx_current =
2170       getTreeInfoFromIdentifier(artwork.gfx_first, GRAPHICS_SUBDIR);
2171   if (artwork.gfx_current == NULL)
2172     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2173
2174   artwork.snd_current =
2175     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2176   if (artwork.snd_current == NULL)
2177     artwork.snd_current =
2178       getTreeInfoFromIdentifier(artwork.snd_first, SOUNDS_SUBDIR);
2179   if (artwork.snd_current == NULL)
2180     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2181
2182   artwork.mus_current =
2183     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2184   if (artwork.mus_current == NULL)
2185     artwork.mus_current =
2186       getTreeInfoFromIdentifier(artwork.mus_first, MUSIC_SUBDIR);
2187   if (artwork.mus_current == NULL)
2188     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2189
2190   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2191   artwork.snd_current_identifier = artwork.snd_current->identifier;
2192   artwork.mus_current_identifier = artwork.mus_current->identifier;
2193
2194 #if 0
2195   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2196   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2197   printf("music set == %s\n\n", artwork.mus_current_identifier);
2198 #endif
2199
2200   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2201   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2202   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2203
2204 #if 0
2205   dumpTreeInfo(artwork.gfx_first, 0);
2206   dumpTreeInfo(artwork.snd_first, 0);
2207   dumpTreeInfo(artwork.mus_first, 0);
2208 #endif
2209 }
2210
2211 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2212                                   LevelDirTree *level_node)
2213 {
2214   /* recursively check all level directories for artwork sub-directories */
2215
2216   while (level_node)
2217   {
2218     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2219                           ARTWORK_DIRECTORY((*artwork_node)->type));
2220
2221 #if 0
2222     if (!level_node->parent_link)
2223       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2224              level_node->filename, level_node->name);
2225 #endif
2226
2227     if (!level_node->parent_link)
2228     {
2229       TreeInfo *topnode_last = *artwork_node;
2230
2231       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2232                                     (*artwork_node)->type);
2233
2234       if (topnode_last != *artwork_node)
2235       {
2236         free((*artwork_node)->identifier);
2237         free((*artwork_node)->name);
2238         free((*artwork_node)->name_sorting);
2239
2240         (*artwork_node)->identifier   = getStringCopy(level_node->filename);
2241         (*artwork_node)->name         = getStringCopy(level_node->name);
2242         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2243
2244         (*artwork_node)->sort_priority = level_node->sort_priority;
2245         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2246       }
2247     }
2248
2249     free(path);
2250
2251     if (level_node->node_group != NULL)
2252       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2253
2254     level_node = level_node->next;
2255   }
2256 }
2257
2258 void LoadLevelArtworkInfo()
2259 {
2260   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2261
2262   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2263   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2264   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2265
2266   /* needed for reloading level artwork not known at ealier stage */
2267   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2268   {
2269     artwork.gfx_current =
2270       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2271     if (artwork.gfx_current == NULL)
2272       artwork.gfx_current =
2273         getTreeInfoFromIdentifier(artwork.gfx_first, GRAPHICS_SUBDIR);
2274     if (artwork.gfx_current == NULL)
2275       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2276   }
2277
2278   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2279   {
2280     artwork.snd_current =
2281       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2282     if (artwork.snd_current == NULL)
2283       artwork.snd_current =
2284         getTreeInfoFromIdentifier(artwork.snd_first, SOUNDS_SUBDIR);
2285     if (artwork.snd_current == NULL)
2286       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2287   }
2288
2289   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2290   {
2291     artwork.mus_current =
2292       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2293     if (artwork.mus_current == NULL)
2294       artwork.mus_current =
2295         getTreeInfoFromIdentifier(artwork.mus_first, MUSIC_SUBDIR);
2296     if (artwork.mus_current == NULL)
2297       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2298   }
2299
2300   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2301   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2302   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2303
2304 #if 0
2305   dumpTreeInfo(artwork.gfx_first, 0);
2306   dumpTreeInfo(artwork.snd_first, 0);
2307   dumpTreeInfo(artwork.mus_first, 0);
2308 #endif
2309 }
2310
2311 static void SaveUserLevelInfo()
2312 {
2313   char *filename;
2314   FILE *file;
2315   int i;
2316
2317   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2318
2319   if (!(file = fopen(filename, MODE_WRITE)))
2320   {
2321     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2322     free(filename);
2323     return;
2324   }
2325
2326   /* always start with reliable default values */
2327   setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
2328
2329 #if 0
2330   /* !!! FIX ME !!! */
2331   setString(&ldi.name, getLoginName());
2332   setString(&ldi.author, getRealName());
2333   ldi.levels = 100;
2334   ldi.first_level = 1;
2335   ldi.sort_priority = LEVELCLASS_USER_START;
2336   ldi.readonly = FALSE;
2337   setString(&ldi.graphics_set, GRAPHICS_SUBDIR);
2338   setString(&ldi.sounds_set, SOUNDS_SUBDIR);
2339   setString(&ldi.music_set, MUSIC_SUBDIR);
2340 #else
2341   ldi.name = getStringCopy(getLoginName());
2342   ldi.author = getStringCopy(getRealName());
2343   ldi.levels = 100;
2344   ldi.first_level = 1;
2345   ldi.sort_priority = LEVELCLASS_USER_START;
2346   ldi.readonly = FALSE;
2347   ldi.graphics_set = getStringCopy(GRAPHICS_SUBDIR);
2348   ldi.sounds_set = getStringCopy(SOUNDS_SUBDIR);
2349   ldi.music_set = getStringCopy(MUSIC_SUBDIR);
2350 #endif
2351
2352   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2353                                                  getCookie("LEVELINFO")));
2354
2355   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2356     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2357         i != LEVELINFO_TOKEN_NAME_SORTING &&
2358         i != LEVELINFO_TOKEN_IMPORTED_FROM)
2359       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2360
2361   fclose(file);
2362   free(filename);
2363
2364   SetFilePermissions(filename, PERMS_PRIVATE);
2365 }
2366
2367 char *getSetupValue(int type, void *value)
2368 {
2369   static char value_string[MAX_LINE_LEN];
2370
2371   if (value == NULL)
2372     return NULL;
2373
2374   switch (type)
2375   {
2376     case TYPE_BOOLEAN:
2377       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2378       break;
2379
2380     case TYPE_SWITCH:
2381       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2382       break;
2383
2384     case TYPE_YES_NO:
2385       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2386       break;
2387
2388     case TYPE_KEY:
2389       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2390       break;
2391
2392     case TYPE_KEY_X11:
2393       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2394       break;
2395
2396     case TYPE_INTEGER:
2397       sprintf(value_string, "%d", *(int *)value);
2398       break;
2399
2400     case TYPE_STRING:
2401       strcpy(value_string, *(char **)value);
2402       break;
2403
2404     default:
2405       value_string[0] = '\0';
2406       break;
2407   }
2408
2409   return value_string;
2410 }
2411
2412 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2413 {
2414   int i;
2415   char *line;
2416   static char token_string[MAX_LINE_LEN];
2417   int token_type = token_info[token_nr].type;
2418   void *setup_value = token_info[token_nr].value;
2419   char *token_text = token_info[token_nr].text;
2420   char *value_string = getSetupValue(token_type, setup_value);
2421
2422   /* build complete token string */
2423   sprintf(token_string, "%s%s", prefix, token_text);
2424
2425   /* build setup entry line */
2426   line = getFormattedSetupEntry(token_string, value_string);
2427
2428   if (token_type == TYPE_KEY_X11)
2429   {
2430     Key key = *(Key *)setup_value;
2431     char *keyname = getKeyNameFromKey(key);
2432
2433     /* add comment, if useful */
2434     if (strcmp(keyname, "(undefined)") != 0 &&
2435         strcmp(keyname, "(unknown)") != 0)
2436     {
2437       /* add at least one whitespace */
2438       strcat(line, " ");
2439       for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2440         strcat(line, " ");
2441
2442       strcat(line, "# ");
2443       strcat(line, keyname);
2444     }
2445   }
2446
2447   return line;
2448 }
2449
2450 void LoadLevelSetup_LastSeries()
2451 {
2452   char *filename;
2453   SetupFileHash *level_setup_hash = NULL;
2454
2455   /* always start with reliable default values */
2456   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2457
2458   /* ----------------------------------------------------------------------- */
2459   /* ~/.<program>/levelsetup.conf                                            */
2460   /* ----------------------------------------------------------------------- */
2461
2462   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2463
2464   if ((level_setup_hash = loadSetupFileHash(filename)))
2465   {
2466     char *last_level_series =
2467       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2468
2469     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2470                                                  last_level_series);
2471     if (leveldir_current == NULL)
2472       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2473
2474     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2475
2476     freeSetupFileHash(level_setup_hash);
2477   }
2478   else
2479     Error(ERR_WARN, "using default setup values");
2480
2481   free(filename);
2482 }
2483
2484 void SaveLevelSetup_LastSeries()
2485 {
2486   char *filename;
2487   char *level_subdir = leveldir_current->filename;
2488   FILE *file;
2489
2490   /* ----------------------------------------------------------------------- */
2491   /* ~/.<program>/levelsetup.conf                                            */
2492   /* ----------------------------------------------------------------------- */
2493
2494   InitUserDataDirectory();
2495
2496   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2497
2498   if (!(file = fopen(filename, MODE_WRITE)))
2499   {
2500     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2501     free(filename);
2502     return;
2503   }
2504
2505   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2506                                                  getCookie("LEVELSETUP")));
2507   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2508                                                level_subdir));
2509
2510   fclose(file);
2511   free(filename);
2512
2513   SetFilePermissions(filename, PERMS_PRIVATE);
2514 }
2515
2516 static void checkSeriesInfo()
2517 {
2518   static char *level_directory = NULL;
2519   DIR *dir;
2520   struct dirent *dir_entry;
2521
2522   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2523
2524   level_directory = getPath2((leveldir_current->user_defined ?
2525                               getUserLevelDir(NULL) :
2526                               options.level_directory),
2527                              leveldir_current->fullpath);
2528
2529   if ((dir = opendir(level_directory)) == NULL)
2530   {
2531     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2532     return;
2533   }
2534
2535   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2536   {
2537     if (strlen(dir_entry->d_name) > 4 &&
2538         dir_entry->d_name[3] == '.' &&
2539         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2540     {
2541       char levelnum_str[4];
2542       int levelnum_value;
2543
2544       strncpy(levelnum_str, dir_entry->d_name, 3);
2545       levelnum_str[3] = '\0';
2546
2547       levelnum_value = atoi(levelnum_str);
2548
2549 #if 0
2550       if (levelnum_value < leveldir_current->first_level)
2551       {
2552         Error(ERR_WARN, "additional level %d found", levelnum_value);
2553         leveldir_current->first_level = levelnum_value;
2554       }
2555       else if (levelnum_value > leveldir_current->last_level)
2556       {
2557         Error(ERR_WARN, "additional level %d found", levelnum_value);
2558         leveldir_current->last_level = levelnum_value;
2559       }
2560 #endif
2561     }
2562   }
2563
2564   closedir(dir);
2565 }
2566
2567 void LoadLevelSetup_SeriesInfo()
2568 {
2569   char *filename;
2570   SetupFileHash *level_setup_hash = NULL;
2571   char *level_subdir = leveldir_current->filename;
2572
2573   /* always start with reliable default values */
2574   level_nr = leveldir_current->first_level;
2575
2576   checkSeriesInfo(leveldir_current);
2577
2578   /* ----------------------------------------------------------------------- */
2579   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2580   /* ----------------------------------------------------------------------- */
2581
2582   level_subdir = leveldir_current->filename;
2583
2584   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2585
2586   if ((level_setup_hash = loadSetupFileHash(filename)))
2587   {
2588     char *token_value;
2589
2590     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2591
2592     if (token_value)
2593     {
2594       level_nr = atoi(token_value);
2595
2596       if (level_nr < leveldir_current->first_level)
2597         level_nr = leveldir_current->first_level;
2598       if (level_nr > leveldir_current->last_level)
2599         level_nr = leveldir_current->last_level;
2600     }
2601
2602     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2603
2604     if (token_value)
2605     {
2606       int level_nr = atoi(token_value);
2607
2608       if (level_nr < leveldir_current->first_level)
2609         level_nr = leveldir_current->first_level;
2610       if (level_nr > leveldir_current->last_level + 1)
2611         level_nr = leveldir_current->last_level;
2612
2613       if (leveldir_current->user_defined)
2614         level_nr = leveldir_current->last_level;
2615
2616       leveldir_current->handicap_level = level_nr;
2617     }
2618
2619     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2620
2621     freeSetupFileHash(level_setup_hash);
2622   }
2623   else
2624     Error(ERR_WARN, "using default setup values");
2625
2626   free(filename);
2627 }
2628
2629 void SaveLevelSetup_SeriesInfo()
2630 {
2631   char *filename;
2632   char *level_subdir = leveldir_current->filename;
2633   char *level_nr_str = int2str(level_nr, 0);
2634   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2635   FILE *file;
2636
2637   /* ----------------------------------------------------------------------- */
2638   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2639   /* ----------------------------------------------------------------------- */
2640
2641   InitLevelSetupDirectory(level_subdir);
2642
2643   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2644
2645   if (!(file = fopen(filename, MODE_WRITE)))
2646   {
2647     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2648     free(filename);
2649     return;
2650   }
2651
2652   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2653                                                  getCookie("LEVELSETUP")));
2654   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2655                                                level_nr_str));
2656   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2657                                                handicap_level_str));
2658
2659   fclose(file);
2660   free(filename);
2661
2662   SetFilePermissions(filename, PERMS_PRIVATE);
2663 }