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