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