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