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