rnd-20020329-1-src
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <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 /* forward declaration for recursive call by "LoadLevelInfoFromSetupFile()" */
1154 static void LoadLevelInfoFromLevelGroupDir(struct LevelDirInfo **,
1155                                            struct LevelDirInfo *,
1156                                            char *);
1157
1158 static boolean LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1159                                          struct LevelDirInfo *node_parent,
1160                                          char *level_directory,
1161                                          char *directory_name)
1162 {
1163   char *directory_path = getPath2(level_directory, directory_name);
1164   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1165   struct SetupFileList *setup_file_list = loadSetupFileList(filename);
1166   struct LevelDirInfo *leveldir_new = NULL;
1167   int i;
1168
1169   if (setup_file_list == NULL)
1170   {
1171     Error(ERR_WARN, "ignoring level directory '%s'", level_directory);
1172
1173     free(directory_path);
1174     free(filename);
1175
1176     return FALSE;
1177   }
1178
1179   leveldir_new = newLevelDirInfo();
1180
1181   checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1182   setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
1183
1184   /* set all structure fields according to the token/value pairs */
1185   ldi = *leveldir_new;
1186   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1187     setSetupInfo(levelinfo_tokens, i,
1188                  getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1189   *leveldir_new = ldi;
1190
1191   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1192
1193   if (leveldir_new->name_short == NULL)
1194     leveldir_new->name_short = getStringCopy(leveldir_new->name);
1195
1196   if (leveldir_new->name_sorting == NULL)
1197     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1198
1199   leveldir_new->filename = getStringCopy(directory_name);
1200
1201   if (node_parent == NULL)              /* top level group */
1202   {
1203     leveldir_new->basepath = level_directory;
1204     leveldir_new->fullpath = leveldir_new->filename;
1205   }
1206   else                                  /* sub level group */
1207   {
1208     leveldir_new->basepath = node_parent->basepath;
1209     leveldir_new->fullpath = getPath2(node_parent->fullpath,
1210                                       directory_name);
1211   }
1212
1213   if (leveldir_new->levels < 1)
1214     leveldir_new->levels = 1;
1215
1216   leveldir_new->last_level =
1217     leveldir_new->first_level + leveldir_new->levels - 1;
1218
1219   leveldir_new->user_defined =
1220     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1221
1222   leveldir_new->color = LEVELCOLOR(leveldir_new);
1223   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1224
1225   leveldir_new->handicap_level =        /* set handicap to default value */
1226     (leveldir_new->user_defined ?
1227      leveldir_new->last_level :
1228      leveldir_new->first_level);
1229
1230   pushLevelDirInfo(node_first, leveldir_new);
1231
1232   freeSetupFileList(setup_file_list);
1233
1234   if (leveldir_new->level_group)
1235   {
1236     /* create node to link back to current level directory */
1237     createParentLevelDirNode(leveldir_new);
1238
1239     /* step into sub-directory and look for more level series */
1240     LoadLevelInfoFromLevelGroupDir(&leveldir_new->node_group,
1241                                    leveldir_new, directory_path);
1242   }
1243
1244   free(directory_path);
1245   free(filename);
1246
1247   return TRUE;
1248 }
1249
1250 static void LoadLevelInfoFromLevelGroupDir(struct LevelDirInfo **node_first,
1251                                            struct LevelDirInfo *node_parent,
1252                                            char *level_directory)
1253 {
1254   DIR *dir;
1255   struct dirent *dir_entry;
1256   boolean valid_entry_found = FALSE;
1257
1258   if ((dir = opendir(level_directory)) == NULL)
1259   {
1260     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1261     return;
1262   }
1263
1264   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1265   {
1266     struct stat file_status;
1267     char *directory_name = dir_entry->d_name;
1268     char *directory_path = getPath2(level_directory, directory_name);
1269
1270     /* skip entries for current and parent directory */
1271     if (strcmp(directory_name, ".")  == 0 ||
1272         strcmp(directory_name, "..") == 0)
1273     {
1274       free(directory_path);
1275       continue;
1276     }
1277
1278     /* find out if directory entry is itself a directory */
1279     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1280         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1281     {
1282       free(directory_path);
1283       continue;
1284     }
1285
1286     free(directory_path);
1287
1288     valid_entry_found |= LoadLevelInfoFromLevelDir(node_first, node_parent,
1289                                                    level_directory,
1290                                                    directory_name);
1291   }
1292
1293   closedir(dir);
1294
1295   if (!valid_entry_found)
1296   {
1297     /* check if this directory directly contains a file "levelinfo.conf" */
1298     valid_entry_found |= LoadLevelInfoFromLevelDir(node_first, node_parent,
1299                                                    level_directory, ".");
1300   }
1301
1302   if (!valid_entry_found)
1303     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1304           level_directory);
1305 }
1306
1307 void LoadLevelInfo()
1308 {
1309   InitUserLevelDirectory(getLoginName());
1310
1311   DrawInitText("Loading level series:", 120, FC_GREEN);
1312
1313   /* check if this directory directly contains a file "levelinfo.conf" */
1314   LoadLevelInfoFromLevelGroupDir(&leveldir_first,NULL,options.level_directory);
1315   LoadLevelInfoFromLevelGroupDir(&leveldir_first,NULL,getUserLevelDir(""));
1316
1317   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1318
1319   if (leveldir_first == NULL)
1320     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1321
1322   sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1323
1324 #if 0
1325   dumpLevelDirInfo(leveldir_first, 0);
1326 #endif
1327 }
1328
1329 static void SaveUserLevelInfo()
1330 {
1331   char *filename;
1332   FILE *file;
1333   int i;
1334
1335   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1336
1337   if (!(file = fopen(filename, MODE_WRITE)))
1338   {
1339     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1340     free(filename);
1341     return;
1342   }
1343
1344   /* always start with reliable default values */
1345   setLevelDirInfoToDefaults(&ldi);
1346
1347   ldi.name = getLoginName();
1348   ldi.author = getRealName();
1349   ldi.levels = 100;
1350   ldi.first_level = 1;
1351   ldi.sort_priority = LEVELCLASS_USER_START;
1352   ldi.readonly = FALSE;
1353
1354   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1355                                                  getCookie("LEVELINFO")));
1356
1357   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1358     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1359         i != LEVELINFO_TOKEN_NAME_SORTING &&
1360         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1361       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1362
1363   fclose(file);
1364   free(filename);
1365
1366   SetFilePermissions(filename, PERMS_PRIVATE);
1367 }
1368
1369 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1370 {
1371   int i;
1372   static char entry[MAX_LINE_LEN];
1373   int token_type = token_info[token_nr].type;
1374   void *setup_value = token_info[token_nr].value;
1375   char *token_text = token_info[token_nr].text;
1376
1377   /* start with the prefix, token and some spaces to format output line */
1378   sprintf(entry, "%s%s:", prefix, token_text);
1379   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1380     strcat(entry, " ");
1381
1382   /* continue with the token's value (which can have different types) */
1383   switch (token_type)
1384   {
1385     case TYPE_BOOLEAN:
1386       strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1387       break;
1388
1389     case TYPE_SWITCH:
1390       strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1391       break;
1392
1393     case TYPE_KEY:
1394       {
1395         Key key = *(Key *)setup_value;
1396         char *keyname = getKeyNameFromKey(key);
1397
1398         strcat(entry, getX11KeyNameFromKey(key));
1399         for (i=strlen(entry); i<50; i++)
1400           strcat(entry, " ");
1401
1402         /* add comment, if useful */
1403         if (strcmp(keyname, "(undefined)") != 0 &&
1404             strcmp(keyname, "(unknown)") != 0)
1405         {
1406           strcat(entry, "# ");
1407           strcat(entry, keyname);
1408         }
1409       }
1410       break;
1411
1412     case TYPE_INTEGER:
1413       {
1414         char buffer[MAX_LINE_LEN];
1415
1416         sprintf(buffer, "%d", *(int *)setup_value);
1417         strcat(entry, buffer);
1418       }
1419       break;
1420
1421     case TYPE_STRING:
1422       strcat(entry, *(char **)setup_value);
1423       break;
1424
1425     default:
1426       break;
1427   }
1428
1429   return entry;
1430 }
1431
1432 void LoadLevelSetup_LastSeries()
1433 {
1434   char *filename;
1435   struct SetupFileList *level_setup_list = NULL;
1436
1437   /* always start with reliable default values */
1438   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1439
1440   /* ----------------------------------------------------------------------- */
1441   /* ~/.<program>/levelsetup.conf                                            */
1442   /* ----------------------------------------------------------------------- */
1443
1444   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1445
1446   if ((level_setup_list = loadSetupFileList(filename)))
1447   {
1448     char *last_level_series =
1449       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1450
1451     leveldir_current = getLevelDirInfoFromFilename(last_level_series);
1452     if (leveldir_current == NULL)
1453       leveldir_current = leveldir_first;
1454
1455     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1456
1457     freeSetupFileList(level_setup_list);
1458   }
1459   else
1460     Error(ERR_WARN, "using default setup values");
1461
1462   free(filename);
1463 }
1464
1465 void SaveLevelSetup_LastSeries()
1466 {
1467   char *filename;
1468   char *level_subdir = leveldir_current->filename;
1469   FILE *file;
1470
1471   /* ----------------------------------------------------------------------- */
1472   /* ~/.<program>/levelsetup.conf                                            */
1473   /* ----------------------------------------------------------------------- */
1474
1475   InitUserDataDirectory();
1476
1477   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1478
1479   if (!(file = fopen(filename, MODE_WRITE)))
1480   {
1481     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1482     free(filename);
1483     return;
1484   }
1485
1486   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1487                                                  getCookie("LEVELSETUP")));
1488   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1489                                                level_subdir));
1490
1491   fclose(file);
1492   free(filename);
1493
1494   SetFilePermissions(filename, PERMS_PRIVATE);
1495 }
1496
1497 static void checkSeriesInfo()
1498 {
1499   static char *level_directory = NULL;
1500   DIR *dir;
1501   struct dirent *dir_entry;
1502
1503   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1504
1505   level_directory = getPath2((leveldir_current->user_defined ?
1506                               getUserLevelDir("") :
1507                               options.level_directory),
1508                              leveldir_current->fullpath);
1509
1510   if ((dir = opendir(level_directory)) == NULL)
1511   {
1512     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1513     return;
1514   }
1515
1516   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
1517   {
1518     if (strlen(dir_entry->d_name) > 4 &&
1519         dir_entry->d_name[3] == '.' &&
1520         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1521     {
1522       char levelnum_str[4];
1523       int levelnum_value;
1524
1525       strncpy(levelnum_str, dir_entry->d_name, 3);
1526       levelnum_str[3] = '\0';
1527
1528       levelnum_value = atoi(levelnum_str);
1529
1530       if (levelnum_value < leveldir_current->first_level)
1531       {
1532         Error(ERR_WARN, "additional level %d found", levelnum_value);
1533         leveldir_current->first_level = levelnum_value;
1534       }
1535       else if (levelnum_value > leveldir_current->last_level)
1536       {
1537         Error(ERR_WARN, "additional level %d found", levelnum_value);
1538         leveldir_current->last_level = levelnum_value;
1539       }
1540     }
1541   }
1542
1543   closedir(dir);
1544 }
1545
1546 void LoadLevelSetup_SeriesInfo()
1547 {
1548   char *filename;
1549   struct SetupFileList *level_setup_list = NULL;
1550   char *level_subdir = leveldir_current->filename;
1551
1552   /* always start with reliable default values */
1553   level_nr = leveldir_current->first_level;
1554
1555   checkSeriesInfo(leveldir_current);
1556
1557   /* ----------------------------------------------------------------------- */
1558   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1559   /* ----------------------------------------------------------------------- */
1560
1561   level_subdir = leveldir_current->filename;
1562
1563   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1564
1565   if ((level_setup_list = loadSetupFileList(filename)))
1566   {
1567     char *token_value;
1568
1569     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1570
1571     if (token_value)
1572     {
1573       level_nr = atoi(token_value);
1574
1575       if (level_nr < leveldir_current->first_level)
1576         level_nr = leveldir_current->first_level;
1577       if (level_nr > leveldir_current->last_level)
1578         level_nr = leveldir_current->last_level;
1579     }
1580
1581     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
1582
1583     if (token_value)
1584     {
1585       int level_nr = atoi(token_value);
1586
1587       if (level_nr < leveldir_current->first_level)
1588         level_nr = leveldir_current->first_level;
1589       if (level_nr > leveldir_current->last_level + 1)
1590         level_nr = leveldir_current->last_level;
1591
1592       if (leveldir_current->user_defined)
1593         level_nr = leveldir_current->last_level;
1594
1595       leveldir_current->handicap_level = level_nr;
1596     }
1597
1598     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1599
1600     freeSetupFileList(level_setup_list);
1601   }
1602   else
1603     Error(ERR_WARN, "using default setup values");
1604
1605   free(filename);
1606 }
1607
1608 void SaveLevelSetup_SeriesInfo()
1609 {
1610   char *filename;
1611   char *level_subdir = leveldir_current->filename;
1612   char *level_nr_str = int2str(level_nr, 0);
1613   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
1614   FILE *file;
1615
1616   /* ----------------------------------------------------------------------- */
1617   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1618   /* ----------------------------------------------------------------------- */
1619
1620   InitLevelSetupDirectory(level_subdir);
1621
1622   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1623
1624   if (!(file = fopen(filename, MODE_WRITE)))
1625   {
1626     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1627     free(filename);
1628     return;
1629   }
1630
1631   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1632                                                  getCookie("LEVELSETUP")));
1633   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
1634                                                level_nr_str));
1635   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
1636                                                handicap_level_str));
1637
1638   fclose(file);
1639   free(filename);
1640
1641   SetFilePermissions(filename, PERMS_PRIVATE);
1642 }