rnd-20020324-2-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 <dirent.h>
15 #include <sys/stat.h>
16 #include <string.h>
17 #include <unistd.h>
18
19 #include "setup.h"
20 #include "joystick.h"
21 #include "text.h"
22 #include "misc.h"
23
24 /* file names and filename extensions */
25 #if !defined(PLATFORM_MSDOS)
26 #define LEVELSETUP_DIRECTORY    "levelsetup"
27 #define SETUP_FILENAME          "setup.conf"
28 #define LEVELSETUP_FILENAME     "levelsetup.conf"
29 #define LEVELINFO_FILENAME      "levelinfo.conf"
30 #define LEVELFILE_EXTENSION     "level"
31 #define TAPEFILE_EXTENSION      "tape"
32 #define SCOREFILE_EXTENSION     "score"
33 #else
34 #define LEVELSETUP_DIRECTORY    "lvlsetup"
35 #define SETUP_FILENAME          "setup.cnf"
36 #define LEVELSETUP_FILENAME     "lvlsetup.cnf"
37 #define LEVELINFO_FILENAME      "lvlinfo.cnf"
38 #define LEVELFILE_EXTENSION     "lvl"
39 #define TAPEFILE_EXTENSION      "tap"
40 #define SCOREFILE_EXTENSION     "sco"
41 #endif
42
43 #define NUM_LEVELCLASS_DESC     8
44 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
45 {
46   "Tutorial Levels",
47   "Classic Originals",
48   "Contributions",
49   "Private Levels",
50   "Boulderdash",
51   "Emerald Mine",
52   "Supaplex",
53   "DX Boulderdash"
54 };
55
56 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE : \
57                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED : \
58                          IS_LEVELCLASS_BD(n) ?                  FC_GREEN : \
59                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW : \
60                          IS_LEVELCLASS_SP(n) ?                  FC_GREEN : \
61                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW : \
62                          IS_LEVELCLASS_CONTRIBUTION(n) ?        FC_GREEN : \
63                          IS_LEVELCLASS_USER(n) ?                FC_RED : \
64                          FC_BLUE)
65
66 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 : \
67                          IS_LEVELCLASS_CLASSICS(n) ?            1 : \
68                          IS_LEVELCLASS_BD(n) ?                  2 : \
69                          IS_LEVELCLASS_EM(n) ?                  3 : \
70                          IS_LEVELCLASS_SP(n) ?                  4 : \
71                          IS_LEVELCLASS_DX(n) ?                  5 : \
72                          IS_LEVELCLASS_CONTRIBUTION(n) ?        6 : \
73                          IS_LEVELCLASS_USER(n) ?                7 : \
74                          9)
75
76 #define TOKEN_VALUE_POSITION            30
77
78 #define MAX_COOKIE_LEN                  256
79
80
81 /* ------------------------------------------------------------------------- */
82 /* file functions                                                            */
83 /* ------------------------------------------------------------------------- */
84
85 char *getLevelClassDescription(struct LevelDirInfo *ldi)
86 {
87   int position = ldi->sort_priority / 100;
88
89   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
90     return levelclass_desc[position];
91   else
92     return "Unknown Level Class";
93 }
94
95 static char *getUserLevelDir(char *level_subdir)
96 {
97   static char *userlevel_dir = NULL;
98   char *data_dir = getUserDataDir();
99   char *userlevel_subdir = LEVELS_DIRECTORY;
100
101   if (userlevel_dir)
102     free(userlevel_dir);
103
104   if (strlen(level_subdir) > 0)
105     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
106   else
107     userlevel_dir = getPath2(data_dir, userlevel_subdir);
108
109   return userlevel_dir;
110 }
111
112 static char *getTapeDir(char *level_subdir)
113 {
114   static char *tape_dir = NULL;
115   char *data_dir = getUserDataDir();
116   char *tape_subdir = TAPES_DIRECTORY;
117
118   if (tape_dir)
119     free(tape_dir);
120
121   if (strlen(level_subdir) > 0)
122     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
123   else
124     tape_dir = getPath2(data_dir, tape_subdir);
125
126   return tape_dir;
127 }
128
129 static char *getScoreDir(char *level_subdir)
130 {
131   static char *score_dir = NULL;
132   char *data_dir = options.rw_base_directory;
133   char *score_subdir = SCORES_DIRECTORY;
134
135   if (score_dir)
136     free(score_dir);
137
138   if (strlen(level_subdir) > 0)
139     score_dir = getPath3(data_dir, score_subdir, level_subdir);
140   else
141     score_dir = getPath2(data_dir, score_subdir);
142
143   return score_dir;
144 }
145
146 static char *getLevelSetupDir(char *level_subdir)
147 {
148   static char *levelsetup_dir = NULL;
149   char *data_dir = getUserDataDir();
150   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
151
152   if (levelsetup_dir)
153     free(levelsetup_dir);
154
155   if (strlen(level_subdir) > 0)
156     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
157   else
158     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
159
160   return levelsetup_dir;
161 }
162
163 char *getLevelFilename(int nr)
164 {
165   static char *filename = NULL;
166   char basename[MAX_FILENAME_LEN];
167
168   if (filename != NULL)
169     free(filename);
170
171   sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
172   filename = getPath3((leveldir_current->user_defined ?
173                        getUserLevelDir("") :
174                        options.level_directory),
175                       leveldir_current->fullpath,
176                       basename);
177
178   return filename;
179 }
180
181 char *getTapeFilename(int nr)
182 {
183   static char *filename = NULL;
184   char basename[MAX_FILENAME_LEN];
185
186   if (filename != NULL)
187     free(filename);
188
189   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
190   filename = getPath2(getTapeDir(leveldir_current->filename), basename);
191
192   return filename;
193 }
194
195 char *getScoreFilename(int nr)
196 {
197   static char *filename = NULL;
198   char basename[MAX_FILENAME_LEN];
199
200   if (filename != NULL)
201     free(filename);
202
203   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
204   filename = getPath2(getScoreDir(leveldir_current->filename), basename);
205
206   return filename;
207 }
208
209 char *getSetupFilename()
210 {
211   static char *filename = NULL;
212
213   if (filename != NULL)
214     free(filename);
215
216   filename = getPath2(getSetupDir(), SETUP_FILENAME);
217
218   return filename;
219 }
220
221 static char *getImageBasename(char *basename)
222 {
223   char *result = basename;
224
225 #if defined(PLATFORM_MSDOS)
226   if (program.filename_prefix != NULL)
227   {
228     int prefix_len = strlen(program.filename_prefix);
229
230     if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
231       result = &basename[prefix_len];
232   }
233 #endif
234
235   return result;
236 }
237
238 char *getImageFilename(char *basename)
239 {
240   static char *filename = NULL;
241
242   if (filename != NULL)
243     free(filename);
244
245   filename = getPath2(options.graphics_directory, getImageBasename(basename));
246
247   return filename;
248 }
249
250 void InitTapeDirectory(char *level_subdir)
251 {
252   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
253   createDirectory(getTapeDir(""), "main tape", PERMS_PRIVATE);
254   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
255 }
256
257 void InitScoreDirectory(char *level_subdir)
258 {
259   createDirectory(getScoreDir(""), "main score", PERMS_PUBLIC);
260   createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
261 }
262
263 static void SaveUserLevelInfo();
264
265 void InitUserLevelDirectory(char *level_subdir)
266 {
267   if (access(getUserLevelDir(level_subdir), F_OK) != 0)
268   {
269     createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
270     createDirectory(getUserLevelDir(""), "main user level", PERMS_PRIVATE);
271     createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
272
273     SaveUserLevelInfo();
274   }
275 }
276
277 void InitLevelSetupDirectory(char *level_subdir)
278 {
279   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
280   createDirectory(getLevelSetupDir(""), "main level setup", PERMS_PRIVATE);
281   createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
282 }
283
284 void ReadChunk_VERS(FILE *file, int *file_version, int *game_version)
285 {
286   int file_version_major, file_version_minor, file_version_patch;
287   int game_version_major, game_version_minor, game_version_patch;
288
289   file_version_major = fgetc(file);
290   file_version_minor = fgetc(file);
291   file_version_patch = fgetc(file);
292   fgetc(file);          /* not used */
293
294   game_version_major = fgetc(file);
295   game_version_minor = fgetc(file);
296   game_version_patch = fgetc(file);
297   fgetc(file);          /* not used */
298
299   *file_version = VERSION_IDENT(file_version_major,
300                                 file_version_minor,
301                                 file_version_patch);
302
303   *game_version = VERSION_IDENT(game_version_major,
304                                 game_version_minor,
305                                 game_version_patch);
306 }
307
308 void WriteChunk_VERS(FILE *file, int file_version, int game_version)
309 {
310   int file_version_major = VERSION_MAJOR(file_version);
311   int file_version_minor = VERSION_MINOR(file_version);
312   int file_version_patch = VERSION_PATCH(file_version);
313   int game_version_major = VERSION_MAJOR(game_version);
314   int game_version_minor = VERSION_MINOR(game_version);
315   int game_version_patch = VERSION_PATCH(game_version);
316
317   fputc(file_version_major, file);
318   fputc(file_version_minor, file);
319   fputc(file_version_patch, file);
320   fputc(0, file);       /* not used */
321
322   fputc(game_version_major, file);
323   fputc(game_version_minor, file);
324   fputc(game_version_patch, file);
325   fputc(0, file);       /* not used */
326 }
327
328
329 /* ------------------------------------------------------------------------- */
330 /* some functions to handle lists of level directories                       */
331 /* ------------------------------------------------------------------------- */
332
333 struct LevelDirInfo *newLevelDirInfo()
334 {
335   return checked_calloc(sizeof(struct LevelDirInfo));
336 }
337
338 void pushLevelDirInfo(struct LevelDirInfo **node_first,
339                       struct LevelDirInfo *node_new)
340 {
341   node_new->next = *node_first;
342   *node_first = node_new;
343 }
344
345 int numLevelDirInfo(struct LevelDirInfo *node)
346 {
347   int num = 0;
348
349   while (node)
350   {
351     num++;
352     node = node->next;
353   }
354
355   return num;
356 }
357
358 boolean validLevelSeries(struct LevelDirInfo *node)
359 {
360   return (node != NULL && !node->node_group && !node->parent_link);
361 }
362
363 struct LevelDirInfo *getFirstValidLevelSeries(struct LevelDirInfo *node)
364 {
365   if (node == NULL)
366   {
367     if (leveldir_first)         /* start with first level directory entry */
368       return getFirstValidLevelSeries(leveldir_first);
369     else
370       return NULL;
371   }
372   else if (node->node_group)    /* enter level group (step down into tree) */
373     return getFirstValidLevelSeries(node->node_group);
374   else if (node->parent_link)   /* skip start entry of level group */
375   {
376     if (node->next)             /* get first real level series entry */
377       return getFirstValidLevelSeries(node->next);
378     else                        /* leave empty level group and go on */
379       return getFirstValidLevelSeries(node->node_parent->next);
380   }
381   else                          /* this seems to be a regular level series */
382     return node;
383 }
384
385 struct LevelDirInfo *getLevelDirInfoFirstGroupEntry(struct LevelDirInfo *node)
386 {
387   if (node == NULL)
388     return NULL;
389
390   if (node->node_parent == NULL)                /* top level group */
391     return leveldir_first;
392   else                                          /* sub level group */
393     return node->node_parent->node_group;
394 }
395
396 int numLevelDirInfoInGroup(struct LevelDirInfo *node)
397 {
398   return numLevelDirInfo(getLevelDirInfoFirstGroupEntry(node));
399 }
400
401 int posLevelDirInfo(struct LevelDirInfo *node)
402 {
403   struct LevelDirInfo *node_cmp = getLevelDirInfoFirstGroupEntry(node);
404   int pos = 0;
405
406   while (node_cmp)
407   {
408     if (node_cmp == node)
409       return pos;
410
411     pos++;
412     node_cmp = node_cmp->next;
413   }
414
415   return 0;
416 }
417
418 struct LevelDirInfo *getLevelDirInfoFromPos(struct LevelDirInfo *node, int pos)
419 {
420   struct LevelDirInfo *node_default = node;
421   int pos_cmp = 0;
422
423   while (node)
424   {
425     if (pos_cmp == pos)
426       return node;
427
428     pos_cmp++;
429     node = node->next;
430   }
431
432   return node_default;
433 }
434
435 struct LevelDirInfo *getLevelDirInfoFromFilenameExt(struct LevelDirInfo *node,
436                                                     char *filename)
437 {
438   if (filename == NULL)
439     return NULL;
440
441   while (node)
442   {
443     if (node->node_group)
444     {
445       struct LevelDirInfo *node_group;
446
447       node_group = getLevelDirInfoFromFilenameExt(node->node_group, filename);
448
449       if (node_group)
450         return node_group;
451     }
452     else if (!node->parent_link)
453     {
454       if (strcmp(filename, node->filename) == 0)
455         return node;
456     }
457
458     node = node->next;
459   }
460
461   return NULL;
462 }
463
464 struct LevelDirInfo *getLevelDirInfoFromFilename(char *filename)
465 {
466   return getLevelDirInfoFromFilenameExt(leveldir_first, filename);
467 }
468
469 void dumpLevelDirInfo(struct LevelDirInfo *node, int depth)
470 {
471   int i;
472
473   while (node)
474   {
475     for (i=0; i<depth * 3; i++)
476       printf(" ");
477
478     printf("filename == '%s'\n", node->filename);
479
480     if (node->node_group != NULL)
481       dumpLevelDirInfo(node->node_group, depth + 1);
482
483     node = node->next;
484   }
485 }
486
487 void sortLevelDirInfo(struct LevelDirInfo **node_first,
488                       int (*compare_function)(const void *, const void *))
489 {
490   int num_nodes = numLevelDirInfo(*node_first);
491   struct LevelDirInfo **sort_array;
492   struct LevelDirInfo *node = *node_first;
493   int i = 0;
494
495   if (num_nodes == 0)
496     return;
497
498   /* allocate array for sorting structure pointers */
499   sort_array = checked_calloc(num_nodes * sizeof(struct LevelDirInfo *));
500
501   /* writing structure pointers to sorting array */
502   while (i < num_nodes && node)         /* double boundary check... */
503   {
504     sort_array[i] = node;
505
506     i++;
507     node = node->next;
508   }
509
510   /* sorting the structure pointers in the sorting array */
511   qsort(sort_array, num_nodes, sizeof(struct LevelDirInfo *),
512         compare_function);
513
514   /* update the linkage of list elements with the sorted node array */
515   for (i=0; i<num_nodes - 1; i++)
516     sort_array[i]->next = sort_array[i + 1];
517   sort_array[num_nodes - 1]->next = NULL;
518
519   /* update the linkage of the main list anchor pointer */
520   *node_first = sort_array[0];
521
522   free(sort_array);
523
524   /* now recursively sort the level group structures */
525   node = *node_first;
526   while (node)
527   {
528     if (node->node_group != NULL)
529       sortLevelDirInfo(&node->node_group, compare_function);
530
531     node = node->next;
532   }
533 }
534
535
536 /* ========================================================================= */
537 /* some stuff from "files.c"                                                 */
538 /* ========================================================================= */
539
540 #if defined(PLATFORM_WIN32)
541 #ifndef S_IRGRP
542 #define S_IRGRP S_IRUSR
543 #endif
544 #ifndef S_IROTH
545 #define S_IROTH S_IRUSR
546 #endif
547 #ifndef S_IWGRP
548 #define S_IWGRP S_IWUSR
549 #endif
550 #ifndef S_IWOTH
551 #define S_IWOTH S_IWUSR
552 #endif
553 #ifndef S_IXGRP
554 #define S_IXGRP S_IXUSR
555 #endif
556 #ifndef S_IXOTH
557 #define S_IXOTH S_IXUSR
558 #endif
559 #ifndef S_IRWXG
560 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
561 #endif
562 #ifndef S_ISGID
563 #define S_ISGID 0
564 #endif
565 #endif  /* PLATFORM_WIN32 */
566
567 /* file permissions for newly written files */
568 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
569 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
570 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
571
572 #define MODE_W_PRIVATE          (S_IWUSR)
573 #define MODE_W_PUBLIC           (S_IWUSR | S_IWGRP)
574 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
575
576 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
577 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
578
579 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
580 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC)
581
582 char *getUserDataDir(void)
583 {
584   static char *userdata_dir = NULL;
585
586   if (!userdata_dir)
587   {
588     char *home_dir = getHomeDir();
589     char *data_dir = program.userdata_directory;
590
591     userdata_dir = getPath2(home_dir, data_dir);
592   }
593
594   return userdata_dir;
595 }
596
597 char *getSetupDir()
598 {
599   return getUserDataDir();
600 }
601
602 static mode_t posix_umask(mode_t mask)
603 {
604 #if defined(PLATFORM_UNIX)
605   return umask(mask);
606 #else
607   return 0;
608 #endif
609 }
610
611 static int posix_mkdir(const char *pathname, mode_t mode)
612 {
613 #if defined(PLATFORM_WIN32)
614   return mkdir(pathname);
615 #else
616   return mkdir(pathname, mode);
617 #endif
618 }
619
620 void createDirectory(char *dir, char *text, int permission_class)
621 {
622   /* leave "other" permissions in umask untouched, but ensure group parts
623      of USERDATA_DIR_MODE are not masked */
624   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
625                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
626   mode_t normal_umask = posix_umask(0);
627   mode_t group_umask = ~(dir_mode & S_IRWXG);
628   posix_umask(normal_umask & group_umask);
629
630   if (access(dir, F_OK) != 0)
631     if (posix_mkdir(dir, dir_mode) != 0)
632       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
633
634   posix_umask(normal_umask);            /* reset normal umask */
635 }
636
637 void InitUserDataDirectory()
638 {
639   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
640 }
641
642 void SetFilePermissions(char *filename, int permission_class)
643 {
644   chmod(filename, (permission_class == PERMS_PRIVATE ?
645                    FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
646 }
647
648 char *getCookie(char *file_type)
649 {
650   static char cookie[MAX_COOKIE_LEN + 1];
651
652   if (strlen(program.cookie_prefix) + 1 +
653       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
654     return "[COOKIE ERROR]";    /* should never happen */
655
656   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
657           program.cookie_prefix, file_type,
658           program.version_major, program.version_minor);
659
660   return cookie;
661 }
662
663 int getFileVersionFromCookieString(const char *cookie)
664 {
665   const char *ptr_cookie1, *ptr_cookie2;
666   const char *pattern1 = "_FILE_VERSION_";
667   const char *pattern2 = "?.?";
668   const int len_cookie = strlen(cookie);
669   const int len_pattern1 = strlen(pattern1);
670   const int len_pattern2 = strlen(pattern2);
671   const int len_pattern = len_pattern1 + len_pattern2;
672   int version_major, version_minor;
673
674   if (len_cookie <= len_pattern)
675     return -1;
676
677   ptr_cookie1 = &cookie[len_cookie - len_pattern];
678   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
679
680   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
681     return -1;
682
683   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
684       ptr_cookie2[1] != '.' ||
685       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
686     return -1;
687
688   version_major = ptr_cookie2[0] - '0';
689   version_minor = ptr_cookie2[2] - '0';
690
691   return VERSION_IDENT(version_major, version_minor, 0);
692 }
693
694 boolean checkCookieString(const char *cookie, const char *template)
695 {
696   const char *pattern = "_FILE_VERSION_?.?";
697   const int len_cookie = strlen(cookie);
698   const int len_template = strlen(template);
699   const int len_pattern = strlen(pattern);
700
701   if (len_cookie != len_template)
702     return FALSE;
703
704   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
705     return FALSE;
706
707   return TRUE;
708 }
709
710 /* ------------------------------------------------------------------------- */
711 /* setup file list handling functions                                        */
712 /* ------------------------------------------------------------------------- */
713
714 int get_string_integer_value(char *s)
715 {
716   static char *number_text[][3] =
717   {
718     { "0", "zero", "null", },
719     { "1", "one", "first" },
720     { "2", "two", "second" },
721     { "3", "three", "third" },
722     { "4", "four", "fourth" },
723     { "5", "five", "fifth" },
724     { "6", "six", "sixth" },
725     { "7", "seven", "seventh" },
726     { "8", "eight", "eighth" },
727     { "9", "nine", "ninth" },
728     { "10", "ten", "tenth" },
729     { "11", "eleven", "eleventh" },
730     { "12", "twelve", "twelfth" },
731   };
732
733   int i, j;
734   char *s_lower = getStringToLower(s);
735   int result = -1;
736
737   for (i=0; i<13; i++)
738     for (j=0; j<3; j++)
739       if (strcmp(s_lower, number_text[i][j]) == 0)
740         result = i;
741
742   if (result == -1)
743     result = atoi(s);
744
745   free(s_lower);
746
747   return result;
748 }
749
750 boolean get_string_boolean_value(char *s)
751 {
752   char *s_lower = getStringToLower(s);
753   boolean result = FALSE;
754
755   if (strcmp(s_lower, "true") == 0 ||
756       strcmp(s_lower, "yes") == 0 ||
757       strcmp(s_lower, "on") == 0 ||
758       get_string_integer_value(s) == 1)
759     result = TRUE;
760
761   free(s_lower);
762
763   return result;
764 }
765
766 char *getFormattedSetupEntry(char *token, char *value)
767 {
768   int i;
769   static char entry[MAX_LINE_LEN];
770
771   sprintf(entry, "%s:", token);
772   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
773     entry[i] = ' ';
774   entry[i] = '\0';
775
776   strcat(entry, value);
777
778   return entry;
779 }
780
781 void freeSetupFileList(struct SetupFileList *setup_file_list)
782 {
783   if (!setup_file_list)
784     return;
785
786   if (setup_file_list->token)
787     free(setup_file_list->token);
788   if (setup_file_list->value)
789     free(setup_file_list->value);
790   if (setup_file_list->next)
791     freeSetupFileList(setup_file_list->next);
792   free(setup_file_list);
793 }
794
795 static struct SetupFileList *newSetupFileList(char *token, char *value)
796 {
797   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
798
799   new->token = checked_malloc(strlen(token) + 1);
800   strcpy(new->token, token);
801
802   new->value = checked_malloc(strlen(value) + 1);
803   strcpy(new->value, value);
804
805   new->next = NULL;
806
807   return new;
808 }
809
810 char *getTokenValue(struct SetupFileList *setup_file_list, char *token)
811 {
812   if (!setup_file_list)
813     return NULL;
814
815   if (strcmp(setup_file_list->token, token) == 0)
816     return setup_file_list->value;
817   else
818     return getTokenValue(setup_file_list->next, token);
819 }
820
821 static void setTokenValue(struct SetupFileList *setup_file_list,
822                           char *token, char *value)
823 {
824   if (!setup_file_list)
825     return;
826
827   if (strcmp(setup_file_list->token, token) == 0)
828   {
829     free(setup_file_list->value);
830     setup_file_list->value = checked_malloc(strlen(value) + 1);
831     strcpy(setup_file_list->value, value);
832   }
833   else if (setup_file_list->next == NULL)
834     setup_file_list->next = newSetupFileList(token, value);
835   else
836     setTokenValue(setup_file_list->next, token, value);
837 }
838
839 #ifdef DEBUG
840 static void printSetupFileList(struct SetupFileList *setup_file_list)
841 {
842   if (!setup_file_list)
843     return;
844
845   printf("token: '%s'\n", setup_file_list->token);
846   printf("value: '%s'\n", setup_file_list->value);
847
848   printSetupFileList(setup_file_list->next);
849 }
850 #endif
851
852 struct SetupFileList *loadSetupFileList(char *filename)
853 {
854   int line_len;
855   char line[MAX_LINE_LEN];
856   char *token, *value, *line_ptr;
857   struct SetupFileList *setup_file_list = newSetupFileList("", "");
858   struct SetupFileList *first_valid_list_entry;
859
860   FILE *file;
861
862   if (!(file = fopen(filename, MODE_READ)))
863   {
864     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
865     return NULL;
866   }
867
868   while(!feof(file))
869   {
870     /* read next line of input file */
871     if (!fgets(line, MAX_LINE_LEN, file))
872       break;
873
874     /* cut trailing comment or whitespace from input line */
875     for (line_ptr = line; *line_ptr; line_ptr++)
876     {
877       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
878       {
879         *line_ptr = '\0';
880         break;
881       }
882     }
883
884     /* cut trailing whitespaces from input line */
885     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
886       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
887         *line_ptr = '\0';
888
889     /* ignore empty lines */
890     if (*line == '\0')
891       continue;
892
893     line_len = strlen(line);
894
895     /* cut leading whitespaces from token */
896     for (token = line; *token; token++)
897       if (*token != ' ' && *token != '\t')
898         break;
899
900     /* find end of token */
901     for (line_ptr = token; *line_ptr; line_ptr++)
902     {
903       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
904       {
905         *line_ptr = '\0';
906         break;
907       }
908     }
909
910     if (line_ptr < line + line_len)
911       value = line_ptr + 1;
912     else
913       value = "\0";
914
915     /* cut leading whitespaces from value */
916     for (; *value; value++)
917       if (*value != ' ' && *value != '\t')
918         break;
919
920     if (*token && *value)
921       setTokenValue(setup_file_list, token, value);
922   }
923
924   fclose(file);
925
926   first_valid_list_entry = setup_file_list->next;
927
928   /* free empty list header */
929   setup_file_list->next = NULL;
930   freeSetupFileList(setup_file_list);
931
932   if (first_valid_list_entry == NULL)
933     Error(ERR_WARN, "configuration file '%s' is empty", filename);
934
935   return first_valid_list_entry;
936 }
937
938 void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
939                                   char *identifier)
940 {
941   if (!setup_file_list)
942     return;
943
944   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
945   {
946     if (!checkCookieString(setup_file_list->value, identifier))
947     {
948       Error(ERR_WARN, "configuration file has wrong file identifier");
949       return;
950     }
951     else
952       return;
953   }
954
955   if (setup_file_list->next)
956     checkSetupFileListIdentifier(setup_file_list->next, identifier);
957   else
958   {
959     Error(ERR_WARN, "configuration file has no file identifier");
960     return;
961   }
962 }
963
964
965 /* ========================================================================= */
966 /* setup file stuff                                                          */
967 /* ========================================================================= */
968
969 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
970 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
971 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
972
973 /* level directory info */
974 #define LEVELINFO_TOKEN_NAME            0
975 #define LEVELINFO_TOKEN_NAME_SHORT      1
976 #define LEVELINFO_TOKEN_NAME_SORTING    2
977 #define LEVELINFO_TOKEN_AUTHOR          3
978 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
979 #define LEVELINFO_TOKEN_LEVELS          5
980 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
981 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
982 #define LEVELINFO_TOKEN_LEVEL_GROUP     8
983 #define LEVELINFO_TOKEN_READONLY        9
984
985 #define NUM_LEVELINFO_TOKENS            10
986
987 static struct LevelDirInfo ldi;
988
989 static struct TokenInfo levelinfo_tokens[] =
990 {
991   /* level directory info */
992   { TYPE_STRING,  &ldi.name,            "name"                          },
993   { TYPE_STRING,  &ldi.name_short,      "name_short"                    },
994   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"                  },
995   { TYPE_STRING,  &ldi.author,          "author"                        },
996   { TYPE_STRING,  &ldi.imported_from,   "imported_from"                 },
997   { TYPE_INTEGER, &ldi.levels,          "levels"                        },
998   { TYPE_INTEGER, &ldi.first_level,     "first_level"                   },
999   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority"                 },
1000   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"                   },
1001   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"                      }
1002 };
1003
1004 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
1005 {
1006   ldi->filename = NULL;
1007   ldi->fullpath = NULL;
1008   ldi->basepath = NULL;
1009   ldi->name = getStringCopy(ANONYMOUS_NAME);
1010   ldi->name_short = NULL;
1011   ldi->name_sorting = NULL;
1012   ldi->author = getStringCopy(ANONYMOUS_NAME);
1013   ldi->imported_from = NULL;
1014   ldi->levels = 0;
1015   ldi->first_level = 0;
1016   ldi->last_level = 0;
1017   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1018   ldi->level_group = FALSE;
1019   ldi->parent_link = FALSE;
1020   ldi->user_defined = FALSE;
1021   ldi->readonly = TRUE;
1022   ldi->color = 0;
1023   ldi->class_desc = NULL;
1024   ldi->handicap_level = 0;
1025   ldi->cl_first = -1;
1026   ldi->cl_cursor = -1;
1027
1028   ldi->node_parent = NULL;
1029   ldi->node_group = NULL;
1030   ldi->next = NULL;
1031 }
1032
1033 static void setLevelDirInfoToDefaultsFromParent(struct LevelDirInfo *ldi,
1034                                                 struct LevelDirInfo *parent)
1035 {
1036   if (parent == NULL)
1037   {
1038     setLevelDirInfoToDefaults(ldi);
1039     return;
1040   }
1041
1042   /* first copy all values from the parent structure ... */
1043   *ldi = *parent;
1044
1045   /* ... then set all fields to default that cannot be inherited from parent.
1046      This is especially important for all those fields that can be set from
1047      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1048      calls 'free()' for all already set token values which requires that no
1049      other structure's pointer may point to them!
1050   */
1051
1052   ldi->filename = NULL;
1053   ldi->fullpath = NULL;
1054   ldi->basepath = NULL;
1055   ldi->name = getStringCopy(ANONYMOUS_NAME);
1056   ldi->name_short = NULL;
1057   ldi->name_sorting = NULL;
1058   ldi->author = getStringCopy(parent->author);
1059   ldi->imported_from = getStringCopy(parent->imported_from);
1060
1061   ldi->level_group = FALSE;
1062   ldi->parent_link = FALSE;
1063
1064   ldi->node_parent = parent;
1065   ldi->node_group = NULL;
1066   ldi->next = NULL;
1067 }
1068
1069 void setSetupInfo(struct TokenInfo *token_info,
1070                   int token_nr, char *token_value)
1071 {
1072   int token_type = token_info[token_nr].type;
1073   void *setup_value = token_info[token_nr].value;
1074
1075   if (token_value == NULL)
1076     return;
1077
1078   /* set setup field to corresponding token value */
1079   switch (token_type)
1080   {
1081     case TYPE_BOOLEAN:
1082     case TYPE_SWITCH:
1083       *(boolean *)setup_value = get_string_boolean_value(token_value);
1084       break;
1085
1086     case TYPE_KEY:
1087       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1088       break;
1089
1090     case TYPE_INTEGER:
1091       *(int *)setup_value = get_string_integer_value(token_value);
1092       break;
1093
1094     case TYPE_STRING:
1095       if (*(char **)setup_value != NULL)
1096         free(*(char **)setup_value);
1097       *(char **)setup_value = getStringCopy(token_value);
1098       break;
1099
1100     default:
1101       break;
1102   }
1103 }
1104
1105 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1106 {
1107   const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
1108   const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
1109   int compare_result;
1110
1111   if (entry1->parent_link || entry2->parent_link)
1112     compare_result = (entry1->parent_link ? -1 : +1);
1113   else if (entry1->sort_priority == entry2->sort_priority)
1114   {
1115     char *name1 = getStringToLower(entry1->name_sorting);
1116     char *name2 = getStringToLower(entry2->name_sorting);
1117
1118     compare_result = strcmp(name1, name2);
1119
1120     free(name1);
1121     free(name2);
1122   }
1123   else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1124     compare_result = entry1->sort_priority - entry2->sort_priority;
1125   else
1126     compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1127
1128   return compare_result;
1129 }
1130
1131 static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
1132 {
1133   struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1134
1135   setLevelDirInfoToDefaults(leveldir_new);
1136
1137   leveldir_new->node_parent = node_parent;
1138   leveldir_new->parent_link = TRUE;
1139
1140   leveldir_new->name = ".. (parent directory)";
1141   leveldir_new->name_short = getStringCopy(leveldir_new->name);
1142   leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1143
1144   leveldir_new->filename = "..";
1145   leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
1146
1147   leveldir_new->sort_priority = node_parent->sort_priority;
1148   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1149
1150   pushLevelDirInfo(&node_parent->node_group, leveldir_new);
1151 }
1152
1153 static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1154                                       struct LevelDirInfo *node_parent,
1155                                       char *level_directory)
1156 {
1157   DIR *dir;
1158   struct dirent *dir_entry;
1159   boolean valid_entry_found = FALSE;
1160
1161   if ((dir = opendir(level_directory)) == NULL)
1162   {
1163     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1164     return;
1165   }
1166
1167   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1168   {
1169     struct SetupFileList *setup_file_list = NULL;
1170     struct stat file_status;
1171     char *directory_name = dir_entry->d_name;
1172     char *directory_path = getPath2(level_directory, directory_name);
1173     char *filename = NULL;
1174
1175     /* skip entries for current and parent directory */
1176     if (strcmp(directory_name, ".")  == 0 ||
1177         strcmp(directory_name, "..") == 0)
1178     {
1179       free(directory_path);
1180       continue;
1181     }
1182
1183     /* find out if directory entry is itself a directory */
1184     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1185         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1186     {
1187       free(directory_path);
1188       continue;
1189     }
1190
1191     filename = getPath2(directory_path, LEVELINFO_FILENAME);
1192     setup_file_list = loadSetupFileList(filename);
1193
1194     if (setup_file_list)
1195     {
1196       struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1197       int i;
1198
1199       checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1200       setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
1201
1202       /* set all structure fields according to the token/value pairs */
1203       ldi = *leveldir_new;
1204       for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1205         setSetupInfo(levelinfo_tokens, i,
1206                      getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1207       *leveldir_new = ldi;
1208
1209       DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1210
1211       if (leveldir_new->name_short == NULL)
1212         leveldir_new->name_short = getStringCopy(leveldir_new->name);
1213
1214       if (leveldir_new->name_sorting == NULL)
1215         leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1216
1217       leveldir_new->filename = getStringCopy(directory_name);
1218
1219       if (node_parent == NULL)          /* top level group */
1220       {
1221         leveldir_new->basepath = level_directory;
1222         leveldir_new->fullpath = leveldir_new->filename;
1223       }
1224       else                              /* sub level group */
1225       {
1226         leveldir_new->basepath = node_parent->basepath;
1227         leveldir_new->fullpath = getPath2(node_parent->fullpath,
1228                                           directory_name);
1229       }
1230
1231       if (leveldir_new->levels < 1)
1232         leveldir_new->levels = 1;
1233
1234       leveldir_new->last_level =
1235         leveldir_new->first_level + leveldir_new->levels - 1;
1236
1237       leveldir_new->user_defined =
1238         (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1239
1240       leveldir_new->color = LEVELCOLOR(leveldir_new);
1241       leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1242
1243       leveldir_new->handicap_level =    /* set handicap to default value */
1244         (leveldir_new->user_defined ?
1245          leveldir_new->last_level :
1246          leveldir_new->first_level);
1247
1248       pushLevelDirInfo(node_first, leveldir_new);
1249
1250       freeSetupFileList(setup_file_list);
1251       valid_entry_found = TRUE;
1252
1253       if (leveldir_new->level_group)
1254       {
1255         /* create node to link back to current level directory */
1256         createParentLevelDirNode(leveldir_new);
1257
1258         /* step into sub-directory and look for more level series */
1259         LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1260                                   leveldir_new, directory_path);
1261       }
1262     }
1263     else
1264       Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1265
1266     free(directory_path);
1267     free(filename);
1268   }
1269
1270   closedir(dir);
1271
1272   if (!valid_entry_found)
1273     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1274           level_directory);
1275 }
1276
1277 void LoadLevelInfo()
1278 {
1279   InitUserLevelDirectory(getLoginName());
1280
1281   DrawInitText("Loading level series:", 120, FC_GREEN);
1282
1283   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1284   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
1285
1286   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1287
1288   if (leveldir_first == NULL)
1289     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1290
1291   sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1292
1293 #if 0
1294   dumpLevelDirInfo(leveldir_first, 0);
1295 #endif
1296 }
1297
1298 static void SaveUserLevelInfo()
1299 {
1300   char *filename;
1301   FILE *file;
1302   int i;
1303
1304   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1305
1306   if (!(file = fopen(filename, MODE_WRITE)))
1307   {
1308     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1309     free(filename);
1310     return;
1311   }
1312
1313   /* always start with reliable default values */
1314   setLevelDirInfoToDefaults(&ldi);
1315
1316   ldi.name = getLoginName();
1317   ldi.author = getRealName();
1318   ldi.levels = 100;
1319   ldi.first_level = 1;
1320   ldi.sort_priority = LEVELCLASS_USER_START;
1321   ldi.readonly = FALSE;
1322
1323   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1324                                                  getCookie("LEVELINFO")));
1325
1326   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1327     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1328         i != LEVELINFO_TOKEN_NAME_SORTING &&
1329         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1330       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1331
1332   fclose(file);
1333   free(filename);
1334
1335   SetFilePermissions(filename, PERMS_PRIVATE);
1336 }
1337
1338 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1339 {
1340   int i;
1341   static char entry[MAX_LINE_LEN];
1342   int token_type = token_info[token_nr].type;
1343   void *setup_value = token_info[token_nr].value;
1344   char *token_text = token_info[token_nr].text;
1345
1346   /* start with the prefix, token and some spaces to format output line */
1347   sprintf(entry, "%s%s:", prefix, token_text);
1348   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1349     strcat(entry, " ");
1350
1351   /* continue with the token's value (which can have different types) */
1352   switch (token_type)
1353   {
1354     case TYPE_BOOLEAN:
1355       strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1356       break;
1357
1358     case TYPE_SWITCH:
1359       strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1360       break;
1361
1362     case TYPE_KEY:
1363       {
1364         Key key = *(Key *)setup_value;
1365         char *keyname = getKeyNameFromKey(key);
1366
1367         strcat(entry, getX11KeyNameFromKey(key));
1368         for (i=strlen(entry); i<50; i++)
1369           strcat(entry, " ");
1370
1371         /* add comment, if useful */
1372         if (strcmp(keyname, "(undefined)") != 0 &&
1373             strcmp(keyname, "(unknown)") != 0)
1374         {
1375           strcat(entry, "# ");
1376           strcat(entry, keyname);
1377         }
1378       }
1379       break;
1380
1381     case TYPE_INTEGER:
1382       {
1383         char buffer[MAX_LINE_LEN];
1384
1385         sprintf(buffer, "%d", *(int *)setup_value);
1386         strcat(entry, buffer);
1387       }
1388       break;
1389
1390     case TYPE_STRING:
1391       strcat(entry, *(char **)setup_value);
1392       break;
1393
1394     default:
1395       break;
1396   }
1397
1398   return entry;
1399 }
1400
1401 void LoadLevelSetup_LastSeries()
1402 {
1403   char *filename;
1404   struct SetupFileList *level_setup_list = NULL;
1405
1406   /* always start with reliable default values */
1407   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1408
1409   /* ----------------------------------------------------------------------- */
1410   /* ~/.<program>/levelsetup.conf                                            */
1411   /* ----------------------------------------------------------------------- */
1412
1413   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1414
1415   if ((level_setup_list = loadSetupFileList(filename)))
1416   {
1417     char *last_level_series =
1418       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1419
1420     leveldir_current = getLevelDirInfoFromFilename(last_level_series);
1421     if (leveldir_current == NULL)
1422       leveldir_current = leveldir_first;
1423
1424     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1425
1426     freeSetupFileList(level_setup_list);
1427   }
1428   else
1429     Error(ERR_WARN, "using default setup values");
1430
1431   free(filename);
1432 }
1433
1434 void SaveLevelSetup_LastSeries()
1435 {
1436   char *filename;
1437   char *level_subdir = leveldir_current->filename;
1438   FILE *file;
1439
1440   /* ----------------------------------------------------------------------- */
1441   /* ~/.<program>/levelsetup.conf                                            */
1442   /* ----------------------------------------------------------------------- */
1443
1444   InitUserDataDirectory();
1445
1446   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1447
1448   if (!(file = fopen(filename, MODE_WRITE)))
1449   {
1450     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1451     free(filename);
1452     return;
1453   }
1454
1455   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1456                                                  getCookie("LEVELSETUP")));
1457   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1458                                                level_subdir));
1459
1460   fclose(file);
1461   free(filename);
1462
1463   SetFilePermissions(filename, PERMS_PRIVATE);
1464 }
1465
1466 static void checkSeriesInfo()
1467 {
1468   static char *level_directory = NULL;
1469   DIR *dir;
1470   struct dirent *dir_entry;
1471
1472   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1473
1474   level_directory = getPath2((leveldir_current->user_defined ?
1475                               getUserLevelDir("") :
1476                               options.level_directory),
1477                              leveldir_current->fullpath);
1478
1479   if ((dir = opendir(level_directory)) == NULL)
1480   {
1481     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1482     return;
1483   }
1484
1485   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
1486   {
1487     if (strlen(dir_entry->d_name) > 4 &&
1488         dir_entry->d_name[3] == '.' &&
1489         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1490     {
1491       char levelnum_str[4];
1492       int levelnum_value;
1493
1494       strncpy(levelnum_str, dir_entry->d_name, 3);
1495       levelnum_str[3] = '\0';
1496
1497       levelnum_value = atoi(levelnum_str);
1498
1499       if (levelnum_value < leveldir_current->first_level)
1500       {
1501         Error(ERR_WARN, "additional level %d found", levelnum_value);
1502         leveldir_current->first_level = levelnum_value;
1503       }
1504       else if (levelnum_value > leveldir_current->last_level)
1505       {
1506         Error(ERR_WARN, "additional level %d found", levelnum_value);
1507         leveldir_current->last_level = levelnum_value;
1508       }
1509     }
1510   }
1511
1512   closedir(dir);
1513 }
1514
1515 void LoadLevelSetup_SeriesInfo()
1516 {
1517   char *filename;
1518   struct SetupFileList *level_setup_list = NULL;
1519   char *level_subdir = leveldir_current->filename;
1520
1521   /* always start with reliable default values */
1522   level_nr = leveldir_current->first_level;
1523
1524   checkSeriesInfo(leveldir_current);
1525
1526   /* ----------------------------------------------------------------------- */
1527   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1528   /* ----------------------------------------------------------------------- */
1529
1530   level_subdir = leveldir_current->filename;
1531
1532   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1533
1534   if ((level_setup_list = loadSetupFileList(filename)))
1535   {
1536     char *token_value;
1537
1538     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1539
1540     if (token_value)
1541     {
1542       level_nr = atoi(token_value);
1543
1544       if (level_nr < leveldir_current->first_level)
1545         level_nr = leveldir_current->first_level;
1546       if (level_nr > leveldir_current->last_level)
1547         level_nr = leveldir_current->last_level;
1548     }
1549
1550     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
1551
1552     if (token_value)
1553     {
1554       int level_nr = atoi(token_value);
1555
1556       if (level_nr < leveldir_current->first_level)
1557         level_nr = leveldir_current->first_level;
1558       if (level_nr > leveldir_current->last_level + 1)
1559         level_nr = leveldir_current->last_level;
1560
1561       if (leveldir_current->user_defined)
1562         level_nr = leveldir_current->last_level;
1563
1564       leveldir_current->handicap_level = level_nr;
1565     }
1566
1567     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1568
1569     freeSetupFileList(level_setup_list);
1570   }
1571   else
1572     Error(ERR_WARN, "using default setup values");
1573
1574   free(filename);
1575 }
1576
1577 void SaveLevelSetup_SeriesInfo()
1578 {
1579   char *filename;
1580   char *level_subdir = leveldir_current->filename;
1581   char *level_nr_str = int2str(level_nr, 0);
1582   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
1583   FILE *file;
1584
1585   /* ----------------------------------------------------------------------- */
1586   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1587   /* ----------------------------------------------------------------------- */
1588
1589   InitLevelSetupDirectory(level_subdir);
1590
1591   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1592
1593   if (!(file = fopen(filename, MODE_WRITE)))
1594   {
1595     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1596     free(filename);
1597     return;
1598   }
1599
1600   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1601                                                  getCookie("LEVELSETUP")));
1602   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
1603                                                level_nr_str));
1604   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
1605                                                handicap_level_str));
1606
1607   fclose(file);
1608   free(filename);
1609
1610   SetFilePermissions(filename, PERMS_PRIVATE);
1611 }