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