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