rnd-20020803-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
25 /* file names and filename extensions */
26 #if !defined(PLATFORM_MSDOS)
27 #define LEVELSETUP_DIRECTORY    "levelsetup"
28 #define SETUP_FILENAME          "setup.conf"
29 #define LEVELSETUP_FILENAME     "levelsetup.conf"
30 #define LEVELINFO_FILENAME      "levelinfo.conf"
31 #define GRAPHICSINFO_FILENAME   "graphicsinfo.conf"
32 #define SOUNDSINFO_FILENAME     "soundsinfo.conf"
33 #define MUSICINFO_FILENAME      "musicinfo.conf"
34 #define LEVELFILE_EXTENSION     "level"
35 #define TAPEFILE_EXTENSION      "tape"
36 #define SCOREFILE_EXTENSION     "score"
37 #else
38 #define LEVELSETUP_DIRECTORY    "lvlsetup"
39 #define SETUP_FILENAME          "setup.cnf"
40 #define LEVELSETUP_FILENAME     "lvlsetup.cnf"
41 #define LEVELINFO_FILENAME      "lvlinfo.cnf"
42 #define GRAPHICSINFO_FILENAME   "gfxinfo.cnf"
43 #define SOUNDSINFO_FILENAME     "sndinfo.cnf"
44 #define MUSICINFO_FILENAME      "musinfo.cnf"
45 #define LEVELFILE_EXTENSION     "lvl"
46 #define TAPEFILE_EXTENSION      "tap"
47 #define SCOREFILE_EXTENSION     "sco"
48 #endif
49
50 #define NUM_LEVELCLASS_DESC     8
51 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
52 {
53   "Tutorial Levels",
54   "Classic Originals",
55   "Contributions",
56   "Private Levels",
57   "Boulderdash",
58   "Emerald Mine",
59   "Supaplex",
60   "DX Boulderdash"
61 };
62
63 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE : \
64                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED : \
65                          IS_LEVELCLASS_BD(n) ?                  FC_GREEN : \
66                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW : \
67                          IS_LEVELCLASS_SP(n) ?                  FC_GREEN : \
68                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW : \
69                          IS_LEVELCLASS_CONTRIBUTION(n) ?        FC_GREEN : \
70                          IS_LEVELCLASS_USER(n) ?                FC_RED : \
71                          FC_BLUE)
72
73 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 : \
74                          IS_LEVELCLASS_CLASSICS(n) ?            1 : \
75                          IS_LEVELCLASS_BD(n) ?                  2 : \
76                          IS_LEVELCLASS_EM(n) ?                  3 : \
77                          IS_LEVELCLASS_SP(n) ?                  4 : \
78                          IS_LEVELCLASS_DX(n) ?                  5 : \
79                          IS_LEVELCLASS_CONTRIBUTION(n) ?        6 : \
80                          IS_LEVELCLASS_USER(n) ?                7 : \
81                          9)
82
83 #define TOKEN_VALUE_POSITION            40
84 #define TOKEN_COMMENT_POSITION          60
85
86 #define MAX_COOKIE_LEN                  256
87
88 #define ARTWORKINFO_FILENAME(type)      ((type) == TREE_TYPE_GRAPHICS_DIR ? \
89                                          GRAPHICSINFO_FILENAME :            \
90                                          (type) == TREE_TYPE_SOUNDS_DIR ?   \
91                                          SOUNDSINFO_FILENAME :              \
92                                          (type) == TREE_TYPE_MUSIC_DIR ?    \
93                                          MUSICINFO_FILENAME : "")
94
95 #define ARTWORK_DIRECTORY(type)         ((type) == TREE_TYPE_GRAPHICS_DIR ? \
96                                          GRAPHICS_DIRECTORY :               \
97                                          (type) == TREE_TYPE_SOUNDS_DIR ?   \
98                                          SOUNDS_DIRECTORY :                 \
99                                          (type) == TREE_TYPE_MUSIC_DIR ?    \
100                                          MUSIC_DIRECTORY : "")
101
102 #define OPTIONS_ARTWORK_DIRECTORY(type) ((type) == TREE_TYPE_GRAPHICS_DIR ? \
103                                          options.graphics_directory :       \
104                                          (type) == TREE_TYPE_SOUNDS_DIR ?   \
105                                          options.sounds_directory :         \
106                                          (type) == TREE_TYPE_MUSIC_DIR ?    \
107                                          options.music_directory : "")
108
109
110 /* ------------------------------------------------------------------------- */
111 /* file functions                                                            */
112 /* ------------------------------------------------------------------------- */
113
114 static char *getLevelClassDescription(TreeInfo *ldi)
115 {
116   int position = ldi->sort_priority / 100;
117
118   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
119     return levelclass_desc[position];
120   else
121     return "Unknown Level Class";
122 }
123
124 static char *getUserLevelDir(char *level_subdir)
125 {
126   static char *userlevel_dir = NULL;
127   char *data_dir = getUserDataDir();
128   char *userlevel_subdir = LEVELS_DIRECTORY;
129
130   if (userlevel_dir)
131     free(userlevel_dir);
132
133   if (level_subdir != NULL)
134     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
135   else
136     userlevel_dir = getPath2(data_dir, userlevel_subdir);
137
138   return userlevel_dir;
139 }
140
141 static char *getTapeDir(char *level_subdir)
142 {
143   static char *tape_dir = NULL;
144   char *data_dir = getUserDataDir();
145   char *tape_subdir = TAPES_DIRECTORY;
146
147   if (tape_dir)
148     free(tape_dir);
149
150   if (level_subdir != NULL)
151     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
152   else
153     tape_dir = getPath2(data_dir, tape_subdir);
154
155   return tape_dir;
156 }
157
158 static char *getScoreDir(char *level_subdir)
159 {
160   static char *score_dir = NULL;
161   char *data_dir = options.rw_base_directory;
162   char *score_subdir = SCORES_DIRECTORY;
163
164   if (score_dir)
165     free(score_dir);
166
167   if (level_subdir != NULL)
168     score_dir = getPath3(data_dir, score_subdir, level_subdir);
169   else
170     score_dir = getPath2(data_dir, score_subdir);
171
172   return score_dir;
173 }
174
175 static char *getLevelSetupDir(char *level_subdir)
176 {
177   static char *levelsetup_dir = NULL;
178   char *data_dir = getUserDataDir();
179   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
180
181   if (levelsetup_dir)
182     free(levelsetup_dir);
183
184   if (level_subdir != NULL)
185     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
186   else
187     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
188
189   return levelsetup_dir;
190 }
191
192 static char *getLevelDirFromTreeInfo(TreeInfo *node)
193 {
194   static char *level_dir = NULL;
195
196   if (node == NULL)
197     return options.level_directory;
198
199   if (level_dir)
200     free(level_dir);
201
202   level_dir = getPath2((node->user_defined ? getUserLevelDir(NULL) :
203                         options.level_directory), node->fullpath);
204
205   return level_dir;
206 }
207
208 static char *getCurrentLevelDir()
209 {
210   return getLevelDirFromTreeInfo(leveldir_current);
211 }
212
213 static char *getDefaultGraphicsDir(char *graphics_subdir)
214 {
215   static char *graphics_dir = NULL;
216
217   if (graphics_subdir == NULL)
218     return options.graphics_directory;
219
220   if (graphics_dir)
221     free(graphics_dir);
222
223   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
224
225   return graphics_dir;
226 }
227
228 static char *getDefaultSoundsDir(char *sounds_subdir)
229 {
230   static char *sounds_dir = NULL;
231
232   if (sounds_subdir == NULL)
233     return options.sounds_directory;
234
235   if (sounds_dir)
236     free(sounds_dir);
237
238   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
239
240   return sounds_dir;
241 }
242
243 static char *getDefaultMusicDir(char *music_subdir)
244 {
245   static char *music_dir = NULL;
246
247   if (music_subdir == NULL)
248     return options.music_directory;
249
250   if (music_dir)
251     free(music_dir);
252
253   music_dir = getPath2(options.music_directory, music_subdir);
254
255   return music_dir;
256 }
257
258 static char *getUserGraphicsDir()
259 {
260   static char *usergraphics_dir = NULL;
261
262   if (usergraphics_dir == NULL)
263     usergraphics_dir = getPath2(getUserDataDir(), GRAPHICS_DIRECTORY);
264
265   return usergraphics_dir;
266 }
267
268 static char *getUserSoundsDir()
269 {
270   static char *usersounds_dir = NULL;
271
272   if (usersounds_dir == NULL)
273     usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
274
275   return usersounds_dir;
276 }
277
278 static char *getUserMusicDir()
279 {
280   static char *usermusic_dir = NULL;
281
282   if (usermusic_dir == NULL)
283     usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
284
285   return usermusic_dir;
286 }
287
288 char *getLevelFilename(int nr)
289 {
290   static char *filename = NULL;
291   char basename[MAX_FILENAME_LEN];
292
293   if (filename != NULL)
294     free(filename);
295
296   sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
297   filename = getPath2(getCurrentLevelDir(), basename);
298
299   return filename;
300 }
301
302 char *getTapeFilename(int nr)
303 {
304   static char *filename = NULL;
305   char basename[MAX_FILENAME_LEN];
306
307   if (filename != NULL)
308     free(filename);
309
310   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
311   filename = getPath2(getTapeDir(leveldir_current->filename), basename);
312
313   return filename;
314 }
315
316 char *getScoreFilename(int nr)
317 {
318   static char *filename = NULL;
319   char basename[MAX_FILENAME_LEN];
320
321   if (filename != NULL)
322     free(filename);
323
324   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
325   filename = getPath2(getScoreDir(leveldir_current->filename), basename);
326
327   return filename;
328 }
329
330 char *getSetupFilename()
331 {
332   static char *filename = NULL;
333
334   if (filename != NULL)
335     free(filename);
336
337   filename = getPath2(getSetupDir(), SETUP_FILENAME);
338
339   return filename;
340 }
341
342 static char *getSetupArtworkDir(TreeInfo *ti)
343 {
344   static char *artwork_dir = NULL;
345
346   if (artwork_dir != NULL)
347     free(artwork_dir);
348
349   artwork_dir = getPath2(ti->basepath, ti->fullpath);
350
351   return artwork_dir;
352 }
353
354 static char *getCorrectedImageBasename(char *basename)
355 {
356   char *result = basename;
357
358 #if defined(PLATFORM_MSDOS)
359   if (program.filename_prefix != NULL)
360   {
361     int prefix_len = strlen(program.filename_prefix);
362
363     if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
364       result = &basename[prefix_len];
365   }
366 #endif
367
368   return result;
369 }
370
371 static boolean fileExists(char *filename)
372 {
373 #if 0
374   printf("checking file '%s'\n", filename);
375 #endif
376
377   return (access(filename, F_OK) == 0);
378 }
379
380 char *getCustomImageFilename(char *basename)
381 {
382   static char *filename = NULL;
383
384   if (filename != NULL)
385     free(filename);
386
387   basename = getCorrectedImageBasename(basename);
388
389   if (!setup.override_level_graphics)
390   {
391     /* 1st try: look for special artwork in current level series directory */
392     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
393     if (fileExists(filename))
394       return filename;
395   }
396
397   /* 2nd try: look for special artwork in configured artwork directory */
398   filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
399   if (fileExists(filename))
400     return filename;
401
402   /* 3rd try: look for default artwork in new default artwork directory */
403   filename = getPath2(getDefaultGraphicsDir(GRAPHICS_SUBDIR), basename);
404   if (fileExists(filename))
405     return filename;
406
407   /* 4th try: look for default artwork in old default artwork directory */
408   filename = getPath2(options.graphics_directory, basename);
409   if (fileExists(filename))
410     return filename;
411
412   return NULL;          /* cannot find specified artwork file anywhere */
413 }
414
415 char *getCustomSoundFilename(char *basename)
416 {
417   static char *filename = NULL;
418
419   if (filename != NULL)
420     free(filename);
421
422   if (!setup.override_level_sounds)
423   {
424     /* 1st try: look for special artwork in current level series directory */
425     filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
426     if (fileExists(filename))
427       return filename;
428   }
429
430   /* 2nd try: look for special artwork in configured artwork directory */
431   filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
432   if (fileExists(filename))
433     return filename;
434
435   /* 3rd try: look for default artwork in new default artwork directory */
436   filename = getPath2(getDefaultSoundsDir(SOUNDS_SUBDIR), basename);
437   if (fileExists(filename))
438     return filename;
439
440   /* 4th try: look for default artwork in old default artwork directory */
441   filename = getPath2(options.sounds_directory, basename);
442   if (fileExists(filename))
443     return filename;
444
445   return NULL;          /* cannot find specified artwork file anywhere */
446 }
447
448 char *getCustomSoundConfigFilename()
449 {
450   return getCustomSoundFilename(SOUNDSINFO_FILENAME);
451 }
452
453 char *getCustomMusicDirectory(void)
454 {
455   static char *directory = NULL;
456
457   if (directory != NULL)
458     free(directory);
459
460   if (!setup.override_level_music)
461   {
462     /* 1st try: look for special artwork in current level series directory */
463     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
464     if (fileExists(directory))
465       return directory;
466   }
467
468   /* 2nd try: look for special artwork in configured artwork directory */
469   directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
470   if (fileExists(directory))
471     return directory;
472
473   /* 3rd try: look for default artwork in new default artwork directory */
474   directory = getStringCopy(getDefaultMusicDir(MUSIC_SUBDIR));
475   if (fileExists(directory))
476     return directory;
477
478   /* 4th try: look for default artwork in old default artwork directory */
479   directory = getStringCopy(options.music_directory);
480   if (fileExists(directory))
481     return directory;
482
483   return NULL;          /* cannot find specified artwork file anywhere */
484 }
485
486 void InitTapeDirectory(char *level_subdir)
487 {
488   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
489   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
490   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
491 }
492
493 void InitScoreDirectory(char *level_subdir)
494 {
495   createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
496   createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
497 }
498
499 static void SaveUserLevelInfo();
500
501 void InitUserLevelDirectory(char *level_subdir)
502 {
503   if (access(getUserLevelDir(level_subdir), F_OK) != 0)
504   {
505     createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
506     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
507     createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
508
509     SaveUserLevelInfo();
510   }
511 }
512
513 void InitLevelSetupDirectory(char *level_subdir)
514 {
515   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
516   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
517   createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
518 }
519
520
521 /* ------------------------------------------------------------------------- */
522 /* some functions to handle lists of level directories                       */
523 /* ------------------------------------------------------------------------- */
524
525 TreeInfo *newTreeInfo()
526 {
527   return checked_calloc(sizeof(TreeInfo));
528 }
529
530 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
531 {
532   node_new->next = *node_first;
533   *node_first = node_new;
534 }
535
536 int numTreeInfo(TreeInfo *node)
537 {
538   int num = 0;
539
540   while (node)
541   {
542     num++;
543     node = node->next;
544   }
545
546   return num;
547 }
548
549 boolean validLevelSeries(TreeInfo *node)
550 {
551   return (node != NULL && !node->node_group && !node->parent_link);
552 }
553
554 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
555 {
556   if (node == NULL)
557     return NULL;
558
559   if (node->node_group)         /* enter level group (step down into tree) */
560     return getFirstValidTreeInfoEntry(node->node_group);
561   else if (node->parent_link)   /* skip start entry of level group */
562   {
563     if (node->next)             /* get first real level series entry */
564       return getFirstValidTreeInfoEntry(node->next);
565     else                        /* leave empty level group and go on */
566       return getFirstValidTreeInfoEntry(node->node_parent->next);
567   }
568   else                          /* this seems to be a regular level series */
569     return node;
570 }
571
572 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
573 {
574   if (node == NULL)
575     return NULL;
576
577   if (node->node_parent == NULL)                /* top level group */
578     return *node->node_top;
579   else                                          /* sub level group */
580     return node->node_parent->node_group;
581 }
582
583 int numTreeInfoInGroup(TreeInfo *node)
584 {
585   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
586 }
587
588 int posTreeInfo(TreeInfo *node)
589 {
590   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
591   int pos = 0;
592
593   while (node_cmp)
594   {
595     if (node_cmp == node)
596       return pos;
597
598     pos++;
599     node_cmp = node_cmp->next;
600   }
601
602   return 0;
603 }
604
605 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
606 {
607   TreeInfo *node_default = node;
608   int pos_cmp = 0;
609
610   while (node)
611   {
612     if (pos_cmp == pos)
613       return node;
614
615     pos_cmp++;
616     node = node->next;
617   }
618
619   return node_default;
620 }
621
622 TreeInfo *getTreeInfoFromFilenameExt(TreeInfo *node, char *filename)
623 {
624   if (filename == NULL)
625     return NULL;
626
627   while (node)
628   {
629     if (node->node_group)
630     {
631       TreeInfo *node_group;
632
633       node_group = getTreeInfoFromFilenameExt(node->node_group, filename);
634
635       if (node_group)
636         return node_group;
637     }
638     else if (!node->parent_link)
639     {
640       if (strcmp(filename, node->filename) == 0)
641         return node;
642
643       /* special case when looking for level series artwork:
644          node->name_short contains level series filename */
645       if (strcmp(node->filename, ".") == 0 &&
646           strcmp(filename, node->name_short) == 0)
647         return node;
648     }
649
650     node = node->next;
651   }
652
653   return NULL;
654 }
655
656 TreeInfo *getTreeInfoFromFilename(TreeInfo *ti, char *filename)
657 {
658   return getTreeInfoFromFilenameExt(ti, filename);
659 }
660
661 void dumpTreeInfo(TreeInfo *node, int depth)
662 {
663   int i;
664
665   printf("Dumping TreeInfo:\n");
666
667   while (node)
668   {
669     for (i=0; i<(depth + 1) * 3; i++)
670       printf(" ");
671
672     printf("filename == '%s' (%s) [%s]\n",
673            node->filename, node->name, node->name_short);
674
675     if (node->node_group != NULL)
676       dumpTreeInfo(node->node_group, depth + 1);
677
678     node = node->next;
679   }
680 }
681
682 void sortTreeInfo(TreeInfo **node_first,
683                   int (*compare_function)(const void *, const void *))
684 {
685   int num_nodes = numTreeInfo(*node_first);
686   TreeInfo **sort_array;
687   TreeInfo *node = *node_first;
688   int i = 0;
689
690   if (num_nodes == 0)
691     return;
692
693   /* allocate array for sorting structure pointers */
694   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
695
696   /* writing structure pointers to sorting array */
697   while (i < num_nodes && node)         /* double boundary check... */
698   {
699     sort_array[i] = node;
700
701     i++;
702     node = node->next;
703   }
704
705   /* sorting the structure pointers in the sorting array */
706   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
707         compare_function);
708
709   /* update the linkage of list elements with the sorted node array */
710   for (i=0; i<num_nodes - 1; i++)
711     sort_array[i]->next = sort_array[i + 1];
712   sort_array[num_nodes - 1]->next = NULL;
713
714   /* update the linkage of the main list anchor pointer */
715   *node_first = sort_array[0];
716
717   free(sort_array);
718
719   /* now recursively sort the level group structures */
720   node = *node_first;
721   while (node)
722   {
723     if (node->node_group != NULL)
724       sortTreeInfo(&node->node_group, compare_function);
725
726     node = node->next;
727   }
728 }
729
730
731 /* ========================================================================= */
732 /* some stuff from "files.c"                                                 */
733 /* ========================================================================= */
734
735 #if defined(PLATFORM_WIN32)
736 #ifndef S_IRGRP
737 #define S_IRGRP S_IRUSR
738 #endif
739 #ifndef S_IROTH
740 #define S_IROTH S_IRUSR
741 #endif
742 #ifndef S_IWGRP
743 #define S_IWGRP S_IWUSR
744 #endif
745 #ifndef S_IWOTH
746 #define S_IWOTH S_IWUSR
747 #endif
748 #ifndef S_IXGRP
749 #define S_IXGRP S_IXUSR
750 #endif
751 #ifndef S_IXOTH
752 #define S_IXOTH S_IXUSR
753 #endif
754 #ifndef S_IRWXG
755 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
756 #endif
757 #ifndef S_ISGID
758 #define S_ISGID 0
759 #endif
760 #endif  /* PLATFORM_WIN32 */
761
762 /* file permissions for newly written files */
763 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
764 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
765 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
766
767 #define MODE_W_PRIVATE          (S_IWUSR)
768 #define MODE_W_PUBLIC           (S_IWUSR | S_IWGRP)
769 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
770
771 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
772 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
773
774 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
775 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC)
776
777 char *getUserDataDir(void)
778 {
779   static char *userdata_dir = NULL;
780
781   if (!userdata_dir)
782   {
783     char *home_dir = getHomeDir();
784     char *data_dir = program.userdata_directory;
785
786     userdata_dir = getPath2(home_dir, data_dir);
787   }
788
789   return userdata_dir;
790 }
791
792 char *getSetupDir()
793 {
794   return getUserDataDir();
795 }
796
797 static mode_t posix_umask(mode_t mask)
798 {
799 #if defined(PLATFORM_UNIX)
800   return umask(mask);
801 #else
802   return 0;
803 #endif
804 }
805
806 static int posix_mkdir(const char *pathname, mode_t mode)
807 {
808 #if defined(PLATFORM_WIN32)
809   return mkdir(pathname);
810 #else
811   return mkdir(pathname, mode);
812 #endif
813 }
814
815 void createDirectory(char *dir, char *text, int permission_class)
816 {
817   /* leave "other" permissions in umask untouched, but ensure group parts
818      of USERDATA_DIR_MODE are not masked */
819   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
820                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
821   mode_t normal_umask = posix_umask(0);
822   mode_t group_umask = ~(dir_mode & S_IRWXG);
823   posix_umask(normal_umask & group_umask);
824
825   if (access(dir, F_OK) != 0)
826     if (posix_mkdir(dir, dir_mode) != 0)
827       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
828
829   posix_umask(normal_umask);            /* reset normal umask */
830 }
831
832 void InitUserDataDirectory()
833 {
834   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
835 }
836
837 void SetFilePermissions(char *filename, int permission_class)
838 {
839   chmod(filename, (permission_class == PERMS_PRIVATE ?
840                    FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
841 }
842
843 char *getCookie(char *file_type)
844 {
845   static char cookie[MAX_COOKIE_LEN + 1];
846
847   if (strlen(program.cookie_prefix) + 1 +
848       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
849     return "[COOKIE ERROR]";    /* should never happen */
850
851   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
852           program.cookie_prefix, file_type,
853           program.version_major, program.version_minor);
854
855   return cookie;
856 }
857
858 int getFileVersionFromCookieString(const char *cookie)
859 {
860   const char *ptr_cookie1, *ptr_cookie2;
861   const char *pattern1 = "_FILE_VERSION_";
862   const char *pattern2 = "?.?";
863   const int len_cookie = strlen(cookie);
864   const int len_pattern1 = strlen(pattern1);
865   const int len_pattern2 = strlen(pattern2);
866   const int len_pattern = len_pattern1 + len_pattern2;
867   int version_major, version_minor;
868
869   if (len_cookie <= len_pattern)
870     return -1;
871
872   ptr_cookie1 = &cookie[len_cookie - len_pattern];
873   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
874
875   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
876     return -1;
877
878   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
879       ptr_cookie2[1] != '.' ||
880       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
881     return -1;
882
883   version_major = ptr_cookie2[0] - '0';
884   version_minor = ptr_cookie2[2] - '0';
885
886   return VERSION_IDENT(version_major, version_minor, 0);
887 }
888
889 boolean checkCookieString(const char *cookie, const char *template)
890 {
891   const char *pattern = "_FILE_VERSION_?.?";
892   const int len_cookie = strlen(cookie);
893   const int len_template = strlen(template);
894   const int len_pattern = strlen(pattern);
895
896   if (len_cookie != len_template)
897     return FALSE;
898
899   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
900     return FALSE;
901
902   return TRUE;
903 }
904
905 /* ------------------------------------------------------------------------- */
906 /* setup file list handling functions                                        */
907 /* ------------------------------------------------------------------------- */
908
909 int get_string_integer_value(char *s)
910 {
911   static char *number_text[][3] =
912   {
913     { "0", "zero", "null", },
914     { "1", "one", "first" },
915     { "2", "two", "second" },
916     { "3", "three", "third" },
917     { "4", "four", "fourth" },
918     { "5", "five", "fifth" },
919     { "6", "six", "sixth" },
920     { "7", "seven", "seventh" },
921     { "8", "eight", "eighth" },
922     { "9", "nine", "ninth" },
923     { "10", "ten", "tenth" },
924     { "11", "eleven", "eleventh" },
925     { "12", "twelve", "twelfth" },
926   };
927
928   int i, j;
929   char *s_lower = getStringToLower(s);
930   int result = -1;
931
932   for (i=0; i<13; i++)
933     for (j=0; j<3; j++)
934       if (strcmp(s_lower, number_text[i][j]) == 0)
935         result = i;
936
937   if (result == -1)
938     result = atoi(s);
939
940   free(s_lower);
941
942   return result;
943 }
944
945 boolean get_string_boolean_value(char *s)
946 {
947   char *s_lower = getStringToLower(s);
948   boolean result = FALSE;
949
950   if (strcmp(s_lower, "true") == 0 ||
951       strcmp(s_lower, "yes") == 0 ||
952       strcmp(s_lower, "on") == 0 ||
953       get_string_integer_value(s) == 1)
954     result = TRUE;
955
956   free(s_lower);
957
958   return result;
959 }
960
961 char *getFormattedSetupEntry(char *token, char *value)
962 {
963   int i;
964   static char entry[MAX_LINE_LEN];
965
966   /* start with the token and some spaces to format output line */
967   sprintf(entry, "%s:", token);
968   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
969     strcat(entry, " ");
970
971   /* continue with the token's value */
972   strcat(entry, value);
973
974   return entry;
975 }
976
977 void freeSetupFileList(struct SetupFileList *setup_file_list)
978 {
979   if (!setup_file_list)
980     return;
981
982   if (setup_file_list->token)
983     free(setup_file_list->token);
984   if (setup_file_list->value)
985     free(setup_file_list->value);
986   if (setup_file_list->next)
987     freeSetupFileList(setup_file_list->next);
988   free(setup_file_list);
989 }
990
991 static struct SetupFileList *newSetupFileList(char *token, char *value)
992 {
993   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
994
995   new->token = checked_malloc(strlen(token) + 1);
996   strcpy(new->token, token);
997
998   new->value = checked_malloc(strlen(value) + 1);
999   strcpy(new->value, value);
1000
1001   new->next = NULL;
1002
1003   return new;
1004 }
1005
1006 char *getTokenValue(struct SetupFileList *setup_file_list, char *token)
1007 {
1008   if (!setup_file_list)
1009     return NULL;
1010
1011   if (strcmp(setup_file_list->token, token) == 0)
1012     return setup_file_list->value;
1013   else
1014     return getTokenValue(setup_file_list->next, token);
1015 }
1016
1017 static void setTokenValue(struct SetupFileList *setup_file_list,
1018                           char *token, char *value)
1019 {
1020   if (!setup_file_list)
1021     return;
1022
1023   if (strcmp(setup_file_list->token, token) == 0)
1024   {
1025     free(setup_file_list->value);
1026     setup_file_list->value = checked_malloc(strlen(value) + 1);
1027     strcpy(setup_file_list->value, value);
1028   }
1029   else if (setup_file_list->next == NULL)
1030     setup_file_list->next = newSetupFileList(token, value);
1031   else
1032     setTokenValue(setup_file_list->next, token, value);
1033 }
1034
1035 #ifdef DEBUG
1036 static void printSetupFileList(struct SetupFileList *setup_file_list)
1037 {
1038   if (!setup_file_list)
1039     return;
1040
1041   printf("token: '%s'\n", setup_file_list->token);
1042   printf("value: '%s'\n", setup_file_list->value);
1043
1044   printSetupFileList(setup_file_list->next);
1045 }
1046 #endif
1047
1048 struct SetupFileList *loadSetupFileList(char *filename)
1049 {
1050   int line_len;
1051   char line[MAX_LINE_LEN];
1052   char *token, *value, *line_ptr;
1053   struct SetupFileList *setup_file_list = newSetupFileList("", "");
1054   struct SetupFileList *first_valid_list_entry;
1055
1056   FILE *file;
1057
1058   if (!(file = fopen(filename, MODE_READ)))
1059   {
1060     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1061     return NULL;
1062   }
1063
1064   while(!feof(file))
1065   {
1066     /* read next line of input file */
1067     if (!fgets(line, MAX_LINE_LEN, file))
1068       break;
1069
1070     /* cut trailing comment or whitespace from input line */
1071     for (line_ptr = line; *line_ptr; line_ptr++)
1072     {
1073       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1074       {
1075         *line_ptr = '\0';
1076         break;
1077       }
1078     }
1079
1080     /* cut trailing whitespaces from input line */
1081     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1082       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1083         *line_ptr = '\0';
1084
1085     /* ignore empty lines */
1086     if (*line == '\0')
1087       continue;
1088
1089     line_len = strlen(line);
1090
1091     /* cut leading whitespaces from token */
1092     for (token = line; *token; token++)
1093       if (*token != ' ' && *token != '\t')
1094         break;
1095
1096     /* find end of token */
1097     for (line_ptr = token; *line_ptr; line_ptr++)
1098     {
1099       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1100       {
1101         *line_ptr = '\0';
1102         break;
1103       }
1104     }
1105
1106     if (line_ptr < line + line_len)
1107       value = line_ptr + 1;
1108     else
1109       value = "\0";
1110
1111     /* cut leading whitespaces from value */
1112     for (; *value; value++)
1113       if (*value != ' ' && *value != '\t')
1114         break;
1115
1116     if (*token && *value)
1117       setTokenValue(setup_file_list, token, value);
1118   }
1119
1120   fclose(file);
1121
1122   first_valid_list_entry = setup_file_list->next;
1123
1124   /* free empty list header */
1125   setup_file_list->next = NULL;
1126   freeSetupFileList(setup_file_list);
1127
1128   if (first_valid_list_entry == NULL)
1129     Error(ERR_WARN, "configuration file '%s' is empty", filename);
1130
1131   return first_valid_list_entry;
1132 }
1133
1134 void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
1135                                   char *identifier)
1136 {
1137   if (!setup_file_list)
1138     return;
1139
1140   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
1141   {
1142     if (!checkCookieString(setup_file_list->value, identifier))
1143     {
1144       Error(ERR_WARN, "configuration file has wrong file identifier");
1145       return;
1146     }
1147     else
1148       return;
1149   }
1150
1151   if (setup_file_list->next)
1152     checkSetupFileListIdentifier(setup_file_list->next, identifier);
1153   else
1154   {
1155     Error(ERR_WARN, "configuration file has no file identifier");
1156     return;
1157   }
1158 }
1159
1160
1161 /* ========================================================================= */
1162 /* setup file stuff                                                          */
1163 /* ========================================================================= */
1164
1165 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1166 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1167 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1168
1169 /* level directory info */
1170 #define LEVELINFO_TOKEN_NAME            0
1171 #define LEVELINFO_TOKEN_NAME_SHORT      1
1172 #define LEVELINFO_TOKEN_NAME_SORTING    2
1173 #define LEVELINFO_TOKEN_AUTHOR          3
1174 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1175 #define LEVELINFO_TOKEN_LEVELS          5
1176 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
1177 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
1178 #define LEVELINFO_TOKEN_LEVEL_GROUP     8
1179 #define LEVELINFO_TOKEN_READONLY        9
1180
1181 #define NUM_LEVELINFO_TOKENS            10
1182
1183 static LevelDirTree ldi;
1184
1185 static struct TokenInfo levelinfo_tokens[] =
1186 {
1187   /* level directory info */
1188   { TYPE_STRING,  &ldi.name,            "name"          },
1189   { TYPE_STRING,  &ldi.name_short,      "name_short"    },
1190   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1191   { TYPE_STRING,  &ldi.author,          "author"        },
1192   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1193   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1194   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1195   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1196   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1197   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      }
1198 };
1199
1200 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1201 {
1202   ldi->type = type;
1203
1204   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1205                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1206                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1207                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1208                    NULL);
1209
1210   ldi->node_parent = NULL;
1211   ldi->node_group = NULL;
1212   ldi->next = NULL;
1213
1214   ldi->cl_first = -1;
1215   ldi->cl_cursor = -1;
1216
1217   ldi->filename = NULL;
1218   ldi->fullpath = NULL;
1219   ldi->basepath = NULL;
1220   ldi->name = getStringCopy(ANONYMOUS_NAME);
1221   ldi->name_short = NULL;
1222   ldi->name_sorting = NULL;
1223   ldi->author = getStringCopy(ANONYMOUS_NAME);
1224
1225   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1226   ldi->parent_link = FALSE;
1227   ldi->user_defined = FALSE;
1228   ldi->color = 0;
1229   ldi->class_desc = NULL;
1230
1231   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1232   {
1233     ldi->imported_from = NULL;
1234     ldi->levels = 0;
1235     ldi->first_level = 0;
1236     ldi->last_level = 0;
1237     ldi->level_group = FALSE;
1238     ldi->handicap_level = 0;
1239     ldi->readonly = TRUE;
1240   }
1241 }
1242
1243 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1244 {
1245   if (parent == NULL)
1246   {
1247     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1248
1249     setTreeInfoToDefaults(ldi, TREE_TYPE_GENERIC);
1250     return;
1251   }
1252
1253   /* first copy all values from the parent structure ... */
1254   *ldi = *parent;
1255
1256   /* ... then set all fields to default that cannot be inherited from parent.
1257      This is especially important for all those fields that can be set from
1258      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1259      calls 'free()' for all already set token values which requires that no
1260      other structure's pointer may point to them!
1261   */
1262
1263   ldi->filename = NULL;
1264   ldi->fullpath = NULL;
1265   ldi->basepath = NULL;
1266   ldi->name = getStringCopy(ANONYMOUS_NAME);
1267   ldi->name_short = NULL;
1268   ldi->name_sorting = NULL;
1269   ldi->author = getStringCopy(parent->author);
1270   ldi->imported_from = getStringCopy(parent->imported_from);
1271
1272   ldi->level_group = FALSE;
1273   ldi->parent_link = FALSE;
1274
1275   ldi->node_top = parent->node_top;
1276   ldi->node_parent = parent;
1277   ldi->node_group = NULL;
1278   ldi->next = NULL;
1279 }
1280
1281 void setSetupInfo(struct TokenInfo *token_info,
1282                   int token_nr, char *token_value)
1283 {
1284   int token_type = token_info[token_nr].type;
1285   void *setup_value = token_info[token_nr].value;
1286
1287   if (token_value == NULL)
1288     return;
1289
1290   /* set setup field to corresponding token value */
1291   switch (token_type)
1292   {
1293     case TYPE_BOOLEAN:
1294     case TYPE_SWITCH:
1295       *(boolean *)setup_value = get_string_boolean_value(token_value);
1296       break;
1297
1298     case TYPE_KEY:
1299       *(Key *)setup_value = getKeyFromKeyName(token_value);
1300       break;
1301
1302     case TYPE_KEY_X11:
1303       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1304       break;
1305
1306     case TYPE_INTEGER:
1307       *(int *)setup_value = get_string_integer_value(token_value);
1308       break;
1309
1310     case TYPE_STRING:
1311       if (*(char **)setup_value != NULL)
1312         free(*(char **)setup_value);
1313       *(char **)setup_value = getStringCopy(token_value);
1314       break;
1315
1316     default:
1317       break;
1318   }
1319 }
1320
1321 static int compareTreeInfoEntries(const void *object1, const void *object2)
1322 {
1323   const TreeInfo *entry1 = *((TreeInfo **)object1);
1324   const TreeInfo *entry2 = *((TreeInfo **)object2);
1325   int compare_result;
1326
1327   if (entry1->parent_link || entry2->parent_link)
1328     compare_result = (entry1->parent_link ? -1 : +1);
1329   else if (entry1->sort_priority == entry2->sort_priority)
1330   {
1331     char *name1 = getStringToLower(entry1->name_sorting);
1332     char *name2 = getStringToLower(entry2->name_sorting);
1333
1334     compare_result = strcmp(name1, name2);
1335
1336     free(name1);
1337     free(name2);
1338   }
1339   else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1340     compare_result = entry1->sort_priority - entry2->sort_priority;
1341   else
1342     compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1343
1344   return compare_result;
1345 }
1346
1347 static void createParentTreeInfoNode(TreeInfo *node_parent)
1348 {
1349   TreeInfo *ti_new;
1350
1351   if (node_parent == NULL)
1352     return;
1353
1354   ti_new = newTreeInfo();
1355   setTreeInfoToDefaults(ti_new, node_parent->type);
1356
1357   ti_new->node_parent = node_parent;
1358   ti_new->parent_link = TRUE;
1359
1360   ti_new->name = ".. (parent directory)";
1361   ti_new->name_short = getStringCopy(ti_new->name);
1362   ti_new->name_sorting = getStringCopy(ti_new->name);
1363
1364   ti_new->filename = "..";
1365   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1366
1367   ti_new->sort_priority = node_parent->sort_priority;
1368   ti_new->class_desc = getLevelClassDescription(ti_new);
1369
1370   pushTreeInfo(&node_parent->node_group, ti_new);
1371 }
1372
1373 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1374 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1375
1376 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1377                                           TreeInfo *node_parent,
1378                                           char *level_directory,
1379                                           char *directory_name)
1380 {
1381   char *directory_path = getPath2(level_directory, directory_name);
1382   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1383   struct SetupFileList *setup_file_list = loadSetupFileList(filename);
1384   LevelDirTree *leveldir_new = NULL;
1385   int i;
1386
1387   if (setup_file_list == NULL)
1388   {
1389     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1390
1391     free(directory_path);
1392     free(filename);
1393
1394     return FALSE;
1395   }
1396
1397   leveldir_new = newTreeInfo();
1398
1399   if (node_parent)
1400     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1401   else
1402     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1403
1404   leveldir_new->filename = getStringCopy(directory_name);
1405
1406   checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1407
1408   /* set all structure fields according to the token/value pairs */
1409   ldi = *leveldir_new;
1410   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1411     setSetupInfo(levelinfo_tokens, i,
1412                  getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1413   *leveldir_new = ldi;
1414
1415   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1416   {
1417     free(leveldir_new->name);
1418     leveldir_new->name = getStringCopy(leveldir_new->filename);
1419   }
1420
1421   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1422
1423   if (leveldir_new->name_short == NULL)
1424     leveldir_new->name_short = getStringCopy(leveldir_new->name);
1425
1426   if (leveldir_new->name_sorting == NULL)
1427     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1428
1429   if (node_parent == NULL)              /* top level group */
1430   {
1431     leveldir_new->basepath = level_directory;
1432     leveldir_new->fullpath = leveldir_new->filename;
1433   }
1434   else                                  /* sub level group */
1435   {
1436     leveldir_new->basepath = node_parent->basepath;
1437     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1438   }
1439
1440   if (leveldir_new->levels < 1)
1441     leveldir_new->levels = 1;
1442
1443   leveldir_new->last_level =
1444     leveldir_new->first_level + leveldir_new->levels - 1;
1445
1446   leveldir_new->user_defined =
1447     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1448
1449   leveldir_new->color = LEVELCOLOR(leveldir_new);
1450   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1451
1452   leveldir_new->handicap_level =        /* set handicap to default value */
1453     (leveldir_new->user_defined ?
1454      leveldir_new->last_level :
1455      leveldir_new->first_level);
1456
1457   pushTreeInfo(node_first, leveldir_new);
1458
1459   freeSetupFileList(setup_file_list);
1460
1461   if (leveldir_new->level_group)
1462   {
1463     /* create node to link back to current level directory */
1464     createParentTreeInfoNode(leveldir_new);
1465
1466     /* step into sub-directory and look for more level series */
1467     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1468                               leveldir_new, directory_path);
1469   }
1470
1471   free(directory_path);
1472   free(filename);
1473
1474   return TRUE;
1475 }
1476
1477 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1478                                       TreeInfo *node_parent,
1479                                       char *level_directory)
1480 {
1481   DIR *dir;
1482   struct dirent *dir_entry;
1483   boolean valid_entry_found = FALSE;
1484
1485   if ((dir = opendir(level_directory)) == NULL)
1486   {
1487     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1488     return;
1489   }
1490
1491   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1492   {
1493     struct stat file_status;
1494     char *directory_name = dir_entry->d_name;
1495     char *directory_path = getPath2(level_directory, directory_name);
1496
1497     /* skip entries for current and parent directory */
1498     if (strcmp(directory_name, ".")  == 0 ||
1499         strcmp(directory_name, "..") == 0)
1500     {
1501       free(directory_path);
1502       continue;
1503     }
1504
1505     /* find out if directory entry is itself a directory */
1506     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1507         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1508     {
1509       free(directory_path);
1510       continue;
1511     }
1512
1513     free(directory_path);
1514
1515     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
1516         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
1517         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
1518       continue;
1519
1520     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1521                                                     level_directory,
1522                                                     directory_name);
1523   }
1524
1525   closedir(dir);
1526
1527   if (!valid_entry_found)
1528   {
1529     /* check if this directory directly contains a file "levelinfo.conf" */
1530     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1531                                                     level_directory, ".");
1532   }
1533
1534   if (!valid_entry_found)
1535     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1536           level_directory);
1537 }
1538
1539 void LoadLevelInfo()
1540 {
1541   InitUserLevelDirectory(getLoginName());
1542
1543   DrawInitText("Loading level series:", 120, FC_GREEN);
1544
1545   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1546   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1547
1548   /* before sorting, the first entries will be from the user directory */
1549   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1550
1551   if (leveldir_first == NULL)
1552     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1553
1554   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1555
1556 #if 0
1557   dumpTreeInfo(leveldir_first, 0);
1558 #endif
1559 }
1560
1561 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1562                                               TreeInfo *node_parent,
1563                                               char *base_directory,
1564                                               char *directory_name, int type)
1565 {
1566   char *directory_path = getPath2(base_directory, directory_name);
1567   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
1568   struct SetupFileList *setup_file_list = NULL;
1569   TreeInfo *artwork_new = NULL;
1570   int i;
1571
1572   if (access(filename, F_OK) == 0)              /* file exists */
1573     setup_file_list = loadSetupFileList(filename);
1574
1575   if (setup_file_list == NULL)  /* no config file -- look for artwork files */
1576   {
1577     DIR *dir;
1578     struct dirent *dir_entry;
1579     boolean valid_file_found = FALSE;
1580
1581     if ((dir = opendir(directory_path)) != NULL)
1582     {
1583       while ((dir_entry = readdir(dir)) != NULL)
1584       {
1585         char *entry_name = dir_entry->d_name;
1586
1587         if (FileIsArtworkType(entry_name, type))
1588         {
1589           valid_file_found = TRUE;
1590           break;
1591         }
1592       }
1593
1594       closedir(dir);
1595     }
1596
1597     if (!valid_file_found)
1598     {
1599       if (strcmp(directory_name, ".") != 0)
1600         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
1601
1602       free(directory_path);
1603       free(filename);
1604
1605       return FALSE;
1606     }
1607   }
1608
1609   artwork_new = newTreeInfo();
1610
1611   if (node_parent)
1612     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1613   else
1614     setTreeInfoToDefaults(artwork_new, type);
1615
1616   artwork_new->filename = getStringCopy(directory_name);
1617
1618   if (setup_file_list)  /* (before defining ".color" and ".class_desc") */
1619   {
1620 #if 0
1621     checkSetupFileListIdentifier(setup_file_list, getCookie("..."));
1622 #endif
1623
1624     /* set all structure fields according to the token/value pairs */
1625     ldi = *artwork_new;
1626     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1627       setSetupInfo(levelinfo_tokens, i,
1628                    getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1629     *artwork_new = ldi;
1630
1631     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
1632     {
1633       free(artwork_new->name);
1634       artwork_new->name = getStringCopy(artwork_new->filename);
1635     }
1636
1637 #if 0
1638     DrawInitText(artwork_new->name, 150, FC_YELLOW);
1639 #endif
1640
1641     if (artwork_new->name_short == NULL)
1642       artwork_new->name_short = getStringCopy(artwork_new->name);
1643
1644     if (artwork_new->name_sorting == NULL)
1645       artwork_new->name_sorting = getStringCopy(artwork_new->name);
1646   }
1647
1648   if (node_parent == NULL)              /* top level group */
1649   {
1650     artwork_new->basepath = getStringCopy(base_directory);
1651     artwork_new->fullpath = getStringCopy(artwork_new->filename);
1652   }
1653   else                                  /* sub level group */
1654   {
1655     artwork_new->basepath = getStringCopy(node_parent->basepath);
1656     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1657   }
1658
1659   artwork_new->user_defined =
1660     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
1661
1662   /* (may use ".sort_priority" from "setup_file_list" above) */
1663   artwork_new->color = LEVELCOLOR(artwork_new);
1664   artwork_new->class_desc = getLevelClassDescription(artwork_new);
1665
1666   if (setup_file_list == NULL)  /* (after determining ".user_defined") */
1667   {
1668     if (artwork_new->name != NULL)
1669       free(artwork_new->name);
1670
1671     if (strcmp(artwork_new->filename, ".") == 0)
1672     {
1673       if (artwork_new->user_defined)
1674       {
1675         artwork_new->name = getStringCopy("private");
1676         artwork_new->sort_priority = LEVELCLASS_USER;
1677       }
1678       else
1679       {
1680         artwork_new->name = getStringCopy("classic");
1681         artwork_new->sort_priority = LEVELCLASS_CLASSICS;
1682       }
1683
1684       artwork_new->color = LEVELCOLOR(artwork_new);
1685       artwork_new->class_desc = getLevelClassDescription(artwork_new);
1686     }
1687     else
1688       artwork_new->name = getStringCopy(artwork_new->filename);
1689
1690     artwork_new->name_short = getStringCopy(artwork_new->name);
1691     artwork_new->name_sorting = getStringCopy(artwork_new->name);
1692   }
1693
1694   DrawInitText(artwork_new->name, 150, FC_YELLOW);
1695
1696   pushTreeInfo(node_first, artwork_new);
1697
1698   freeSetupFileList(setup_file_list);
1699
1700   free(directory_path);
1701   free(filename);
1702
1703   return TRUE;
1704 }
1705
1706 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
1707                                           TreeInfo *node_parent,
1708                                           char *base_directory, int type)
1709 {
1710   DIR *dir;
1711   struct dirent *dir_entry;
1712   boolean valid_entry_found = FALSE;
1713
1714   if ((dir = opendir(base_directory)) == NULL)
1715   {
1716     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
1717       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
1718     return;
1719   }
1720
1721   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1722   {
1723     struct stat file_status;
1724     char *directory_name = dir_entry->d_name;
1725     char *directory_path = getPath2(base_directory, directory_name);
1726
1727     /* skip entries for current and parent directory */
1728     if (strcmp(directory_name, ".")  == 0 ||
1729         strcmp(directory_name, "..") == 0)
1730     {
1731       free(directory_path);
1732       continue;
1733     }
1734
1735     /* find out if directory entry is itself a directory */
1736     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1737         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1738     {
1739       free(directory_path);
1740       continue;
1741     }
1742
1743     free(directory_path);
1744
1745     /* check if this directory contains artwork with or without config file */
1746     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1747                                                         base_directory,
1748                                                         directory_name, type);
1749   }
1750
1751   closedir(dir);
1752
1753   /* check if this directory directly contains artwork itself */
1754   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1755                                                       base_directory, ".",
1756                                                       type);
1757   if (!valid_entry_found)
1758     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
1759           base_directory);
1760 }
1761
1762 static TreeInfo *getDummyArtworkInfo(int type)
1763 {
1764   /* this is only needed when there is completely no artwork available */
1765   TreeInfo *artwork_new = newTreeInfo();
1766
1767   setTreeInfoToDefaults(artwork_new, type);
1768
1769   artwork_new->filename = getStringCopy(NOT_AVAILABLE);
1770   artwork_new->fullpath = getStringCopy(NOT_AVAILABLE);
1771   artwork_new->basepath = getStringCopy(NOT_AVAILABLE);
1772
1773   if (artwork_new->name != NULL)
1774     free(artwork_new->name);
1775
1776   artwork_new->name         = getStringCopy(NOT_AVAILABLE);
1777   artwork_new->name_short   = getStringCopy(NOT_AVAILABLE);
1778   artwork_new->name_sorting = getStringCopy(NOT_AVAILABLE);
1779
1780   return artwork_new;
1781 }
1782
1783 void LoadArtworkInfo()
1784 {
1785   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
1786
1787   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1788                                 options.graphics_directory,
1789                                 TREE_TYPE_GRAPHICS_DIR);
1790   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1791                                 getUserGraphicsDir(),
1792                                 TREE_TYPE_GRAPHICS_DIR);
1793
1794   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1795                                 options.sounds_directory,
1796                                 TREE_TYPE_SOUNDS_DIR);
1797   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1798                                 getUserSoundsDir(),
1799                                 TREE_TYPE_SOUNDS_DIR);
1800
1801   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1802                                 options.music_directory,
1803                                 TREE_TYPE_MUSIC_DIR);
1804   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1805                                 getUserMusicDir(),
1806                                 TREE_TYPE_MUSIC_DIR);
1807
1808   if (artwork.gfx_first == NULL)
1809     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
1810   if (artwork.snd_first == NULL)
1811     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
1812   if (artwork.mus_first == NULL)
1813     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
1814
1815   /* before sorting, the first entries will be from the user directory */
1816   artwork.gfx_current =
1817     getTreeInfoFromFilename(artwork.gfx_first, setup.graphics_set);
1818   if (artwork.gfx_current == NULL)
1819     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
1820
1821   artwork.snd_current =
1822     getTreeInfoFromFilename(artwork.snd_first, setup.sounds_set);
1823   if (artwork.snd_current == NULL)
1824     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
1825
1826   artwork.mus_current =
1827     getTreeInfoFromFilename(artwork.mus_first, setup.music_set);
1828   if (artwork.mus_current == NULL)
1829     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
1830
1831   artwork.graphics_set_current_name = artwork.gfx_current->name;
1832   artwork.sounds_set_current_name = artwork.snd_current->name;
1833   artwork.music_set_current_name = artwork.mus_current->name;
1834
1835 #if 0
1836   printf("graphics set == %s\n\n", artwork.graphics_set_current_name);
1837   printf("sounds set == %s\n\n", artwork.sounds_set_current_name);
1838   printf("music set == %s\n\n", artwork.music_set_current_name);
1839 #endif
1840
1841   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
1842   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
1843   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
1844
1845 #if 0
1846   dumpTreeInfo(artwork.gfx_first, 0);
1847   dumpTreeInfo(artwork.snd_first, 0);
1848   dumpTreeInfo(artwork.mus_first, 0);
1849 #endif
1850 }
1851
1852 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
1853                                   LevelDirTree *level_node)
1854 {
1855   /* recursively check all level directories for artwork sub-directories */
1856
1857   while (level_node)
1858   {
1859     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
1860                           ARTWORK_DIRECTORY((*artwork_node)->type));
1861
1862 #if 0
1863     if (!level_node->parent_link)
1864       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
1865              level_node->filename, level_node->name);
1866 #endif
1867
1868     if (!level_node->parent_link)
1869     {
1870       TreeInfo *topnode_last = *artwork_node;
1871
1872       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
1873                                     (*artwork_node)->type);
1874
1875       if (topnode_last != *artwork_node)
1876       {
1877         free((*artwork_node)->name);
1878         free((*artwork_node)->name_sorting);
1879         free((*artwork_node)->name_short);
1880
1881         (*artwork_node)->name         = getStringCopy(level_node->name);
1882         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
1883         (*artwork_node)->name_short   = getStringCopy(level_node->filename);
1884
1885         (*artwork_node)->sort_priority = level_node->sort_priority;
1886         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
1887       }
1888     }
1889
1890     free(path);
1891
1892     if (level_node->node_group != NULL)
1893       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
1894
1895     level_node = level_node->next;
1896   }
1897 }
1898
1899 void LoadLevelArtworkInfo()
1900 {
1901   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
1902
1903   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
1904   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
1905   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
1906
1907   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
1908   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
1909   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
1910
1911 #if 0
1912   dumpTreeInfo(artwork.gfx_first, 0);
1913   dumpTreeInfo(artwork.snd_first, 0);
1914   dumpTreeInfo(artwork.mus_first, 0);
1915 #endif
1916 }
1917
1918 static void SaveUserLevelInfo()
1919 {
1920   char *filename;
1921   FILE *file;
1922   int i;
1923
1924   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1925
1926   if (!(file = fopen(filename, MODE_WRITE)))
1927   {
1928     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1929     free(filename);
1930     return;
1931   }
1932
1933   /* always start with reliable default values */
1934   setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
1935
1936   ldi.name = getStringCopy(getLoginName());
1937   ldi.author = getStringCopy(getRealName());
1938   ldi.levels = 100;
1939   ldi.first_level = 1;
1940   ldi.sort_priority = LEVELCLASS_USER_START;
1941   ldi.readonly = FALSE;
1942
1943   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1944                                                  getCookie("LEVELINFO")));
1945
1946   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1947     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1948         i != LEVELINFO_TOKEN_NAME_SORTING &&
1949         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1950       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1951
1952   fclose(file);
1953   free(filename);
1954
1955   SetFilePermissions(filename, PERMS_PRIVATE);
1956 }
1957
1958 char *getSetupValue(int type, void *value)
1959 {
1960   static char value_string[MAX_LINE_LEN];
1961
1962   if (value == NULL)
1963     return NULL;
1964
1965   switch (type)
1966   {
1967     case TYPE_BOOLEAN:
1968       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
1969       break;
1970
1971     case TYPE_SWITCH:
1972       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
1973       break;
1974
1975     case TYPE_YES_NO:
1976       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
1977       break;
1978
1979     case TYPE_KEY:
1980       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
1981       break;
1982
1983     case TYPE_KEY_X11:
1984       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
1985       break;
1986
1987     case TYPE_INTEGER:
1988       sprintf(value_string, "%d", *(int *)value);
1989       break;
1990
1991     case TYPE_STRING:
1992       strcpy(value_string, *(char **)value);
1993       break;
1994
1995     default:
1996       value_string[0] = '\0';
1997       break;
1998   }
1999
2000   return value_string;
2001 }
2002
2003 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2004 {
2005   int i;
2006   char *line;
2007   static char token_string[MAX_LINE_LEN];
2008   int token_type = token_info[token_nr].type;
2009   void *setup_value = token_info[token_nr].value;
2010   char *token_text = token_info[token_nr].text;
2011   char *value_string = getSetupValue(token_type, setup_value);
2012
2013   /* build complete token string */
2014   sprintf(token_string, "%s%s", prefix, token_text);
2015
2016   /* build setup entry line */
2017   line = getFormattedSetupEntry(token_string, value_string);
2018
2019   if (token_type == TYPE_KEY_X11)
2020   {
2021     Key key = *(Key *)setup_value;
2022     char *keyname = getKeyNameFromKey(key);
2023
2024     /* add comment, if useful */
2025     if (strcmp(keyname, "(undefined)") != 0 &&
2026         strcmp(keyname, "(unknown)") != 0)
2027     {
2028       /* add at least one whitespace */
2029       strcat(line, " ");
2030       for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2031         strcat(line, " ");
2032
2033       strcat(line, "# ");
2034       strcat(line, keyname);
2035     }
2036   }
2037
2038   return line;
2039 }
2040
2041 void LoadLevelSetup_LastSeries()
2042 {
2043   char *filename;
2044   struct SetupFileList *level_setup_list = NULL;
2045
2046   /* always start with reliable default values */
2047   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2048
2049   /* ----------------------------------------------------------------------- */
2050   /* ~/.<program>/levelsetup.conf                                            */
2051   /* ----------------------------------------------------------------------- */
2052
2053   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2054
2055   if ((level_setup_list = loadSetupFileList(filename)))
2056   {
2057     char *last_level_series =
2058       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
2059
2060     leveldir_current = getTreeInfoFromFilename(leveldir_first,
2061                                                last_level_series);
2062     if (leveldir_current == NULL)
2063       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2064
2065     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
2066
2067     freeSetupFileList(level_setup_list);
2068   }
2069   else
2070     Error(ERR_WARN, "using default setup values");
2071
2072   free(filename);
2073 }
2074
2075 void SaveLevelSetup_LastSeries()
2076 {
2077   char *filename;
2078   char *level_subdir = leveldir_current->filename;
2079   FILE *file;
2080
2081   /* ----------------------------------------------------------------------- */
2082   /* ~/.<program>/levelsetup.conf                                            */
2083   /* ----------------------------------------------------------------------- */
2084
2085   InitUserDataDirectory();
2086
2087   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2088
2089   if (!(file = fopen(filename, MODE_WRITE)))
2090   {
2091     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2092     free(filename);
2093     return;
2094   }
2095
2096   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2097                                                  getCookie("LEVELSETUP")));
2098   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2099                                                level_subdir));
2100
2101   fclose(file);
2102   free(filename);
2103
2104   SetFilePermissions(filename, PERMS_PRIVATE);
2105 }
2106
2107 static void checkSeriesInfo()
2108 {
2109   static char *level_directory = NULL;
2110   DIR *dir;
2111   struct dirent *dir_entry;
2112
2113   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2114
2115   level_directory = getPath2((leveldir_current->user_defined ?
2116                               getUserLevelDir(NULL) :
2117                               options.level_directory),
2118                              leveldir_current->fullpath);
2119
2120   if ((dir = opendir(level_directory)) == NULL)
2121   {
2122     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2123     return;
2124   }
2125
2126   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2127   {
2128     if (strlen(dir_entry->d_name) > 4 &&
2129         dir_entry->d_name[3] == '.' &&
2130         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2131     {
2132       char levelnum_str[4];
2133       int levelnum_value;
2134
2135       strncpy(levelnum_str, dir_entry->d_name, 3);
2136       levelnum_str[3] = '\0';
2137
2138       levelnum_value = atoi(levelnum_str);
2139
2140       if (levelnum_value < leveldir_current->first_level)
2141       {
2142         Error(ERR_WARN, "additional level %d found", levelnum_value);
2143         leveldir_current->first_level = levelnum_value;
2144       }
2145       else if (levelnum_value > leveldir_current->last_level)
2146       {
2147         Error(ERR_WARN, "additional level %d found", levelnum_value);
2148         leveldir_current->last_level = levelnum_value;
2149       }
2150     }
2151   }
2152
2153   closedir(dir);
2154 }
2155
2156 void LoadLevelSetup_SeriesInfo()
2157 {
2158   char *filename;
2159   struct SetupFileList *level_setup_list = NULL;
2160   char *level_subdir = leveldir_current->filename;
2161
2162   /* always start with reliable default values */
2163   level_nr = leveldir_current->first_level;
2164
2165   checkSeriesInfo(leveldir_current);
2166
2167   /* ----------------------------------------------------------------------- */
2168   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2169   /* ----------------------------------------------------------------------- */
2170
2171   level_subdir = leveldir_current->filename;
2172
2173   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2174
2175   if ((level_setup_list = loadSetupFileList(filename)))
2176   {
2177     char *token_value;
2178
2179     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
2180
2181     if (token_value)
2182     {
2183       level_nr = atoi(token_value);
2184
2185       if (level_nr < leveldir_current->first_level)
2186         level_nr = leveldir_current->first_level;
2187       if (level_nr > leveldir_current->last_level)
2188         level_nr = leveldir_current->last_level;
2189     }
2190
2191     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
2192
2193     if (token_value)
2194     {
2195       int level_nr = atoi(token_value);
2196
2197       if (level_nr < leveldir_current->first_level)
2198         level_nr = leveldir_current->first_level;
2199       if (level_nr > leveldir_current->last_level + 1)
2200         level_nr = leveldir_current->last_level;
2201
2202       if (leveldir_current->user_defined)
2203         level_nr = leveldir_current->last_level;
2204
2205       leveldir_current->handicap_level = level_nr;
2206     }
2207
2208     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
2209
2210     freeSetupFileList(level_setup_list);
2211   }
2212   else
2213     Error(ERR_WARN, "using default setup values");
2214
2215   free(filename);
2216 }
2217
2218 void SaveLevelSetup_SeriesInfo()
2219 {
2220   char *filename;
2221   char *level_subdir = leveldir_current->filename;
2222   char *level_nr_str = int2str(level_nr, 0);
2223   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2224   FILE *file;
2225
2226   /* ----------------------------------------------------------------------- */
2227   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2228   /* ----------------------------------------------------------------------- */
2229
2230   InitLevelSetupDirectory(level_subdir);
2231
2232   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2233
2234   if (!(file = fopen(filename, MODE_WRITE)))
2235   {
2236     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2237     free(filename);
2238     return;
2239   }
2240
2241   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2242                                                  getCookie("LEVELSETUP")));
2243   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2244                                                level_nr_str));
2245   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2246                                                handicap_level_str));
2247
2248   fclose(file);
2249   free(filename);
2250
2251   SetFilePermissions(filename, PERMS_PRIVATE);
2252 }