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