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