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