rnd-20020402-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 /* 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 is expected to be already set! */
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
1096   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1097   ldi->parent_link = FALSE;
1098   ldi->user_defined = FALSE;
1099   ldi->color = 0;
1100   ldi->class_desc = NULL;
1101
1102   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1103   {
1104     ldi->imported_from = NULL;
1105     ldi->levels = 0;
1106     ldi->first_level = 0;
1107     ldi->last_level = 0;
1108     ldi->level_group = FALSE;
1109     ldi->handicap_level = 0;
1110     ldi->readonly = TRUE;
1111   }
1112 }
1113
1114 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1115 {
1116   if (parent == NULL)
1117   {
1118     setTreeInfoToDefaults(ldi);
1119     return;
1120   }
1121
1122   /* first copy all values from the parent structure ... */
1123   *ldi = *parent;
1124
1125   /* ... then set all fields to default that cannot be inherited from parent.
1126      This is especially important for all those fields that can be set from
1127      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1128      calls 'free()' for all already set token values which requires that no
1129      other structure's pointer may point to them!
1130   */
1131
1132   ldi->filename = NULL;
1133   ldi->fullpath = NULL;
1134   ldi->basepath = NULL;
1135   ldi->name = getStringCopy(ANONYMOUS_NAME);
1136   ldi->name_short = NULL;
1137   ldi->name_sorting = NULL;
1138   ldi->author = getStringCopy(parent->author);
1139   ldi->imported_from = getStringCopy(parent->imported_from);
1140
1141   ldi->level_group = FALSE;
1142   ldi->parent_link = FALSE;
1143
1144   ldi->node_parent = parent;
1145   ldi->node_group = NULL;
1146   ldi->next = NULL;
1147 }
1148
1149 void setSetupInfo(struct TokenInfo *token_info,
1150                   int token_nr, char *token_value)
1151 {
1152   int token_type = token_info[token_nr].type;
1153   void *setup_value = token_info[token_nr].value;
1154
1155   if (token_value == NULL)
1156     return;
1157
1158   /* set setup field to corresponding token value */
1159   switch (token_type)
1160   {
1161     case TYPE_BOOLEAN:
1162     case TYPE_SWITCH:
1163       *(boolean *)setup_value = get_string_boolean_value(token_value);
1164       break;
1165
1166     case TYPE_KEY:
1167       *(Key *)setup_value = getKeyFromKeyName(token_value);
1168       break;
1169
1170     case TYPE_KEY_X11:
1171       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1172       break;
1173
1174     case TYPE_INTEGER:
1175       *(int *)setup_value = get_string_integer_value(token_value);
1176       break;
1177
1178     case TYPE_STRING:
1179       if (*(char **)setup_value != NULL)
1180         free(*(char **)setup_value);
1181       *(char **)setup_value = getStringCopy(token_value);
1182       break;
1183
1184     default:
1185       break;
1186   }
1187 }
1188
1189 static int compareTreeInfoEntries(const void *object1, const void *object2)
1190 {
1191   const TreeInfo *entry1 = *((TreeInfo **)object1);
1192   const TreeInfo *entry2 = *((TreeInfo **)object2);
1193   int compare_result;
1194
1195   if (entry1->parent_link || entry2->parent_link)
1196     compare_result = (entry1->parent_link ? -1 : +1);
1197   else if (entry1->sort_priority == entry2->sort_priority)
1198   {
1199     char *name1 = getStringToLower(entry1->name_sorting);
1200     char *name2 = getStringToLower(entry2->name_sorting);
1201
1202     compare_result = strcmp(name1, name2);
1203
1204     free(name1);
1205     free(name2);
1206   }
1207   else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1208     compare_result = entry1->sort_priority - entry2->sort_priority;
1209   else
1210     compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1211
1212   return compare_result;
1213 }
1214
1215 static void createParentTreeInfoNode(TreeInfo *node_parent)
1216 {
1217   TreeInfo *ti_new;
1218
1219   if (node_parent == NULL)
1220     return;
1221
1222   ti_new = newTreeInfo();
1223   ti_new->type = node_parent->type;
1224
1225   setTreeInfoToDefaults(ti_new);
1226
1227   ti_new->node_parent = node_parent;
1228   ti_new->parent_link = TRUE;
1229
1230   ti_new->name = ".. (parent directory)";
1231   ti_new->name_short = getStringCopy(ti_new->name);
1232   ti_new->name_sorting = getStringCopy(ti_new->name);
1233
1234   ti_new->filename = "..";
1235   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1236
1237   ti_new->sort_priority = node_parent->sort_priority;
1238   ti_new->class_desc = getLevelClassDescription(ti_new);
1239
1240   pushTreeInfo(&node_parent->node_group, ti_new);
1241 }
1242
1243 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1244 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1245
1246 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1247                                           TreeInfo *node_parent,
1248                                           char *level_directory,
1249                                           char *directory_name)
1250 {
1251   char *directory_path = getPath2(level_directory, directory_name);
1252   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1253   struct SetupFileList *setup_file_list = loadSetupFileList(filename);
1254   LevelDirTree *leveldir_new = NULL;
1255   int i;
1256
1257   if (setup_file_list == NULL)
1258   {
1259     Error(ERR_WARN, "ignoring level directory '%s'", level_directory);
1260
1261     free(directory_path);
1262     free(filename);
1263
1264     return FALSE;
1265   }
1266
1267   leveldir_new = newTreeInfo();
1268   leveldir_new->type = TREE_TYPE_LEVEL_DIR;
1269
1270   checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1271   setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1272
1273   /* set all structure fields according to the token/value pairs */
1274   ldi = *leveldir_new;
1275   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1276     setSetupInfo(levelinfo_tokens, i,
1277                  getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1278   *leveldir_new = ldi;
1279
1280   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1281
1282   if (leveldir_new->name_short == NULL)
1283     leveldir_new->name_short = getStringCopy(leveldir_new->name);
1284
1285   if (leveldir_new->name_sorting == NULL)
1286     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1287
1288   leveldir_new->filename = getStringCopy(directory_name);
1289
1290   if (node_parent == NULL)              /* top level group */
1291   {
1292     leveldir_new->basepath = level_directory;
1293     leveldir_new->fullpath = leveldir_new->filename;
1294   }
1295   else                                  /* sub level group */
1296   {
1297     leveldir_new->basepath = node_parent->basepath;
1298     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1299   }
1300
1301   if (leveldir_new->levels < 1)
1302     leveldir_new->levels = 1;
1303
1304   leveldir_new->last_level =
1305     leveldir_new->first_level + leveldir_new->levels - 1;
1306
1307   leveldir_new->user_defined =
1308     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1309
1310   leveldir_new->color = LEVELCOLOR(leveldir_new);
1311   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1312
1313   leveldir_new->handicap_level =        /* set handicap to default value */
1314     (leveldir_new->user_defined ?
1315      leveldir_new->last_level :
1316      leveldir_new->first_level);
1317
1318   pushTreeInfo(node_first, leveldir_new);
1319
1320   freeSetupFileList(setup_file_list);
1321
1322   if (leveldir_new->level_group)
1323   {
1324     /* create node to link back to current level directory */
1325     createParentTreeInfoNode(leveldir_new);
1326
1327     /* step into sub-directory and look for more level series */
1328     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1329                               leveldir_new, directory_path);
1330   }
1331
1332   free(directory_path);
1333   free(filename);
1334
1335   return TRUE;
1336 }
1337
1338 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1339                                       TreeInfo *node_parent,
1340                                       char *level_directory)
1341 {
1342   DIR *dir;
1343   struct dirent *dir_entry;
1344   boolean valid_entry_found = FALSE;
1345
1346   if ((dir = opendir(level_directory)) == NULL)
1347   {
1348     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1349     return;
1350   }
1351
1352   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1353   {
1354     struct stat file_status;
1355     char *directory_name = dir_entry->d_name;
1356     char *directory_path = getPath2(level_directory, directory_name);
1357
1358     /* skip entries for current and parent directory */
1359     if (strcmp(directory_name, ".")  == 0 ||
1360         strcmp(directory_name, "..") == 0)
1361     {
1362       free(directory_path);
1363       continue;
1364     }
1365
1366     /* find out if directory entry is itself a directory */
1367     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1368         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1369     {
1370       free(directory_path);
1371       continue;
1372     }
1373
1374     free(directory_path);
1375
1376     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1377                                                     level_directory,
1378                                                     directory_name);
1379   }
1380
1381   closedir(dir);
1382
1383   if (!valid_entry_found)
1384   {
1385     /* check if this directory directly contains a file "levelinfo.conf" */
1386     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1387                                                     level_directory, ".");
1388   }
1389
1390   if (!valid_entry_found)
1391     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1392           level_directory);
1393 }
1394
1395 void LoadLevelInfo()
1396 {
1397   InitUserLevelDirectory(getLoginName());
1398
1399   DrawInitText("Loading level series:", 120, FC_GREEN);
1400
1401   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1402   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1403
1404   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1405
1406   if (leveldir_first == NULL)
1407     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1408
1409   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1410
1411 #if 0
1412   dumpTreeInfo(leveldir_first, 0);
1413 #endif
1414 }
1415
1416 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1417                                               TreeInfo *node_parent,
1418                                               char *base_directory,
1419                                               char *directory_name, int type)
1420 {
1421   char *directory_path = getPath2(base_directory, directory_name);
1422   char *filename =
1423     getPath2(directory_path,
1424              (type == TREE_TYPE_GRAPHICS_DIR ? GRAPHICSINFO_FILENAME :
1425               type == TREE_TYPE_SOUNDS_DIR ? SOUNDSINFO_FILENAME :
1426               type == TREE_TYPE_MUSIC_DIR ? MUSICINFO_FILENAME : ""));
1427   struct SetupFileList *setup_file_list = NULL;
1428   TreeInfo *artwork_new = NULL;
1429   char *check_dir = NULL;
1430   int i;
1431
1432   if (access(getUserLevelDir(filename), F_OK) == 0)     /* file exists */
1433     loadSetupFileList(filename);
1434
1435   if (setup_file_list == NULL)  /* no config file -- look for artwork files */
1436   {
1437     DIR *dir;
1438     struct dirent *dir_entry;
1439     boolean valid_file_found = FALSE;
1440
1441     if ((dir = opendir(base_directory)) != NULL)
1442     {
1443       while ((dir_entry = readdir(dir)) != NULL)
1444       {
1445         char *entry_name = dir_entry->d_name;
1446
1447         if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(entry_name)) ||
1448             (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(entry_name)) ||
1449             (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(entry_name)))
1450         {
1451           valid_file_found = TRUE;
1452           break;
1453         }
1454       }
1455
1456       closedir(dir);
1457     }
1458
1459     if (!valid_file_found)
1460     {
1461       Error(ERR_WARN, "ignoring artwork directory '%s'", base_directory);
1462
1463       free(directory_path);
1464       free(filename);
1465
1466       return FALSE;
1467     }
1468   }
1469
1470   artwork_new = newTreeInfo();
1471   artwork_new->type = type;
1472
1473   setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1474
1475   artwork_new->filename = getStringCopy(directory_name);
1476
1477   if (setup_file_list)
1478   {
1479 #if 0
1480     checkSetupFileListIdentifier(setup_file_list, getCookie("..."));
1481 #endif
1482
1483     /* set all structure fields according to the token/value pairs */
1484     ldi = *artwork_new;
1485     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1486       setSetupInfo(levelinfo_tokens, i,
1487                    getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1488     *artwork_new = ldi;
1489
1490     DrawInitText(artwork_new->name, 150, FC_YELLOW);
1491
1492     if (artwork_new->name_short == NULL)
1493       artwork_new->name_short = getStringCopy(artwork_new->name);
1494
1495     if (artwork_new->name_sorting == NULL)
1496       artwork_new->name_sorting = getStringCopy(artwork_new->name);
1497   }
1498   else
1499   {
1500     if (artwork_new->name != NULL)
1501       free(artwork_new->name);
1502
1503     if (strcmp(artwork_new->filename, ".") == 0)
1504       artwork_new->name = getStringCopy("default");
1505     else
1506       artwork_new->name = getStringCopy(artwork_new->filename);
1507
1508     artwork_new->name_short = getStringCopy(artwork_new->name);
1509     artwork_new->name_sorting = getStringCopy(artwork_new->name);
1510   }
1511
1512   if (node_parent == NULL)              /* top level group */
1513   {
1514     artwork_new->basepath = base_directory;
1515     artwork_new->fullpath = artwork_new->filename;
1516   }
1517   else                                  /* sub level group */
1518   {
1519     artwork_new->basepath = node_parent->basepath;
1520     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1521   }
1522
1523   check_dir = (type == TREE_TYPE_GRAPHICS_DIR ? options.graphics_directory :
1524                type == TREE_TYPE_SOUNDS_DIR ? options.sounds_directory :
1525                type == TREE_TYPE_MUSIC_DIR ? options.music_directory : "");
1526   artwork_new->user_defined =
1527     (artwork_new->basepath == check_dir ? FALSE : TRUE);
1528
1529 #if 0
1530   artwork_new->color = LEVELCOLOR(artwork_new);
1531   artwork_new->class_desc = getLevelClassDescription(artwork_new);
1532 #endif
1533
1534   pushTreeInfo(node_first, artwork_new);
1535
1536   freeSetupFileList(setup_file_list);
1537
1538   free(directory_path);
1539   free(filename);
1540
1541   return TRUE;
1542 }
1543
1544 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
1545                                           TreeInfo *node_parent,
1546                                           char *base_directory, int type)
1547 {
1548   DIR *dir;
1549   struct dirent *dir_entry;
1550   boolean valid_entry_found = FALSE;
1551
1552   if ((dir = opendir(base_directory)) == NULL)
1553   {
1554     if ((type == TREE_TYPE_GRAPHICS_DIR &&
1555          base_directory == options.graphics_directory) ||
1556         (type == TREE_TYPE_SOUNDS_DIR &&
1557          base_directory == options.sounds_directory) ||
1558         (type == TREE_TYPE_MUSIC_DIR &&
1559          base_directory == options.music_directory))
1560       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
1561     return;
1562   }
1563
1564   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1565   {
1566     struct stat file_status;
1567     char *directory_name = dir_entry->d_name;
1568     char *directory_path = getPath2(base_directory, directory_name);
1569
1570     /* skip entries for current and parent directory */
1571     if (strcmp(directory_name, ".")  == 0 ||
1572         strcmp(directory_name, "..") == 0)
1573     {
1574       free(directory_path);
1575       continue;
1576     }
1577
1578     /* find out if directory entry is itself a directory */
1579     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1580         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1581     {
1582       free(directory_path);
1583       continue;
1584     }
1585
1586     free(directory_path);
1587
1588     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1589                                                         base_directory,
1590                                                         directory_name, type);
1591   }
1592
1593   closedir(dir);
1594
1595   if (!valid_entry_found)
1596   {
1597     /* check if this directory directly contains an artwork config file */
1598     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1599                                                         base_directory, ".",
1600                                                         type);
1601   }
1602
1603   if (!valid_entry_found)
1604     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
1605           base_directory);
1606 }
1607
1608 void LoadArtworkInfo()
1609 {
1610   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
1611
1612   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1613                                 options.graphics_directory,
1614                                 TREE_TYPE_GRAPHICS_DIR);
1615   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1616                                 getUserGraphicsDir(NULL),
1617                                 TREE_TYPE_GRAPHICS_DIR);
1618
1619   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1620                                 options.sounds_directory,
1621                                 TREE_TYPE_SOUNDS_DIR);
1622   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1623                                 getUserSoundsDir(NULL),
1624                                 TREE_TYPE_SOUNDS_DIR);
1625
1626   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1627                                 options.music_directory,
1628                                 TREE_TYPE_MUSIC_DIR);
1629   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1630                                 getUserMusicDir(NULL),
1631                                 TREE_TYPE_MUSIC_DIR);
1632
1633   artwork.gfx_current = artwork.gfx_first;
1634   artwork.snd_current = artwork.snd_first;
1635   artwork.mus_current = artwork.mus_first;
1636
1637   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
1638   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
1639   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
1640
1641 #if 0
1642   dumpTreeInfo(artwork.gfx_first, 0);
1643   dumpTreeInfo(artwork.snd_first, 0);
1644   dumpTreeInfo(artwork.mus_first, 0);
1645 #endif
1646 }
1647
1648 static void SaveUserLevelInfo()
1649 {
1650   char *filename;
1651   FILE *file;
1652   int i;
1653
1654   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1655
1656   if (!(file = fopen(filename, MODE_WRITE)))
1657   {
1658     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1659     free(filename);
1660     return;
1661   }
1662
1663   /* always start with reliable default values */
1664   setTreeInfoToDefaults(&ldi);
1665
1666   ldi.name = getLoginName();
1667   ldi.author = getRealName();
1668   ldi.levels = 100;
1669   ldi.first_level = 1;
1670   ldi.sort_priority = LEVELCLASS_USER_START;
1671   ldi.readonly = FALSE;
1672
1673   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1674                                                  getCookie("LEVELINFO")));
1675
1676   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1677     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1678         i != LEVELINFO_TOKEN_NAME_SORTING &&
1679         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1680       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1681
1682   fclose(file);
1683   free(filename);
1684
1685   SetFilePermissions(filename, PERMS_PRIVATE);
1686 }
1687
1688 char *getSetupValue(int type, void *value)
1689 {
1690   static char value_string[MAX_LINE_LEN];
1691
1692   switch (type)
1693   {
1694     case TYPE_BOOLEAN:
1695       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
1696       break;
1697
1698     case TYPE_SWITCH:
1699       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
1700       break;
1701
1702     case TYPE_YES_NO:
1703       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
1704       break;
1705
1706     case TYPE_KEY:
1707       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
1708       break;
1709
1710     case TYPE_KEY_X11:
1711       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
1712       break;
1713
1714     case TYPE_INTEGER:
1715       sprintf(value_string, "%d", *(int *)value);
1716       break;
1717
1718     case TYPE_STRING:
1719       strcpy(value_string, *(char **)value);
1720       break;
1721
1722     default:
1723       value_string[0] = '\0';
1724       break;
1725   }
1726
1727   return value_string;
1728 }
1729
1730 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1731 {
1732   int i;
1733   static char entry[MAX_LINE_LEN];
1734   int token_type = token_info[token_nr].type;
1735   void *setup_value = token_info[token_nr].value;
1736   char *token_text = token_info[token_nr].text;
1737   char *value_string = getSetupValue(token_type, setup_value);
1738
1739   /* start with the prefix, token and some spaces to format output line */
1740   sprintf(entry, "%s%s:", prefix, token_text);
1741   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1742     strcat(entry, " ");
1743
1744   /* continue with the token's value (which can have different types) */
1745   strcat(entry, value_string);
1746
1747   if (token_type == TYPE_KEY_X11)
1748   {
1749     Key key = *(Key *)setup_value;
1750     char *keyname = getKeyNameFromKey(key);
1751
1752     /* add comment, if useful */
1753     if (strcmp(keyname, "(undefined)") != 0 &&
1754         strcmp(keyname, "(unknown)") != 0)
1755     {
1756       for (i=strlen(entry); i<50; i++)
1757         strcat(entry, " ");
1758
1759       strcat(entry, "# ");
1760       strcat(entry, keyname);
1761     }
1762   }
1763
1764   return entry;
1765 }
1766
1767 void LoadLevelSetup_LastSeries()
1768 {
1769   char *filename;
1770   struct SetupFileList *level_setup_list = NULL;
1771
1772   /* always start with reliable default values */
1773   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1774
1775   /* ----------------------------------------------------------------------- */
1776   /* ~/.<program>/levelsetup.conf                                            */
1777   /* ----------------------------------------------------------------------- */
1778
1779   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1780
1781   if ((level_setup_list = loadSetupFileList(filename)))
1782   {
1783     char *last_level_series =
1784       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1785
1786     leveldir_current = getTreeInfoFromFilename(last_level_series);
1787     if (leveldir_current == NULL)
1788       leveldir_current = leveldir_first;
1789
1790     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1791
1792     freeSetupFileList(level_setup_list);
1793   }
1794   else
1795     Error(ERR_WARN, "using default setup values");
1796
1797   free(filename);
1798 }
1799
1800 void SaveLevelSetup_LastSeries()
1801 {
1802   char *filename;
1803   char *level_subdir = leveldir_current->filename;
1804   FILE *file;
1805
1806   /* ----------------------------------------------------------------------- */
1807   /* ~/.<program>/levelsetup.conf                                            */
1808   /* ----------------------------------------------------------------------- */
1809
1810   InitUserDataDirectory();
1811
1812   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1813
1814   if (!(file = fopen(filename, MODE_WRITE)))
1815   {
1816     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1817     free(filename);
1818     return;
1819   }
1820
1821   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1822                                                  getCookie("LEVELSETUP")));
1823   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1824                                                level_subdir));
1825
1826   fclose(file);
1827   free(filename);
1828
1829   SetFilePermissions(filename, PERMS_PRIVATE);
1830 }
1831
1832 static void checkSeriesInfo()
1833 {
1834   static char *level_directory = NULL;
1835   DIR *dir;
1836   struct dirent *dir_entry;
1837
1838   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1839
1840   level_directory = getPath2((leveldir_current->user_defined ?
1841                               getUserLevelDir(NULL) :
1842                               options.level_directory),
1843                              leveldir_current->fullpath);
1844
1845   if ((dir = opendir(level_directory)) == NULL)
1846   {
1847     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1848     return;
1849   }
1850
1851   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
1852   {
1853     if (strlen(dir_entry->d_name) > 4 &&
1854         dir_entry->d_name[3] == '.' &&
1855         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1856     {
1857       char levelnum_str[4];
1858       int levelnum_value;
1859
1860       strncpy(levelnum_str, dir_entry->d_name, 3);
1861       levelnum_str[3] = '\0';
1862
1863       levelnum_value = atoi(levelnum_str);
1864
1865       if (levelnum_value < leveldir_current->first_level)
1866       {
1867         Error(ERR_WARN, "additional level %d found", levelnum_value);
1868         leveldir_current->first_level = levelnum_value;
1869       }
1870       else if (levelnum_value > leveldir_current->last_level)
1871       {
1872         Error(ERR_WARN, "additional level %d found", levelnum_value);
1873         leveldir_current->last_level = levelnum_value;
1874       }
1875     }
1876   }
1877
1878   closedir(dir);
1879 }
1880
1881 void LoadLevelSetup_SeriesInfo()
1882 {
1883   char *filename;
1884   struct SetupFileList *level_setup_list = NULL;
1885   char *level_subdir = leveldir_current->filename;
1886
1887   /* always start with reliable default values */
1888   level_nr = leveldir_current->first_level;
1889
1890   checkSeriesInfo(leveldir_current);
1891
1892   /* ----------------------------------------------------------------------- */
1893   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1894   /* ----------------------------------------------------------------------- */
1895
1896   level_subdir = leveldir_current->filename;
1897
1898   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1899
1900   if ((level_setup_list = loadSetupFileList(filename)))
1901   {
1902     char *token_value;
1903
1904     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1905
1906     if (token_value)
1907     {
1908       level_nr = atoi(token_value);
1909
1910       if (level_nr < leveldir_current->first_level)
1911         level_nr = leveldir_current->first_level;
1912       if (level_nr > leveldir_current->last_level)
1913         level_nr = leveldir_current->last_level;
1914     }
1915
1916     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
1917
1918     if (token_value)
1919     {
1920       int level_nr = atoi(token_value);
1921
1922       if (level_nr < leveldir_current->first_level)
1923         level_nr = leveldir_current->first_level;
1924       if (level_nr > leveldir_current->last_level + 1)
1925         level_nr = leveldir_current->last_level;
1926
1927       if (leveldir_current->user_defined)
1928         level_nr = leveldir_current->last_level;
1929
1930       leveldir_current->handicap_level = level_nr;
1931     }
1932
1933     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1934
1935     freeSetupFileList(level_setup_list);
1936   }
1937   else
1938     Error(ERR_WARN, "using default setup values");
1939
1940   free(filename);
1941 }
1942
1943 void SaveLevelSetup_SeriesInfo()
1944 {
1945   char *filename;
1946   char *level_subdir = leveldir_current->filename;
1947   char *level_nr_str = int2str(level_nr, 0);
1948   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
1949   FILE *file;
1950
1951   /* ----------------------------------------------------------------------- */
1952   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1953   /* ----------------------------------------------------------------------- */
1954
1955   InitLevelSetupDirectory(level_subdir);
1956
1957   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1958
1959   if (!(file = fopen(filename, MODE_WRITE)))
1960   {
1961     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1962     free(filename);
1963     return;
1964   }
1965
1966   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1967                                                  getCookie("LEVELSETUP")));
1968   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
1969                                                level_nr_str));
1970   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
1971                                                handicap_level_str));
1972
1973   fclose(file);
1974   free(filename);
1975
1976   SetFilePermissions(filename, PERMS_PRIVATE);
1977 }