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