rnd-20020404-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 *node->node_top;
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(TreeInfo *ti, char *filename)
538 {
539   return getTreeInfoFromFilenameExt(ti, filename);
540 }
541
542 void dumpTreeInfo(TreeInfo *node, int depth)
543 {
544   int i;
545
546   printf("Dumping TreeInfo:\n");
547
548   while (node)
549   {
550     for (i=0; i<(depth + 1) * 3; i++)
551       printf(" ");
552
553     printf("filename == '%s' [%s]\n", node->filename, node->name);
554
555     if (node->node_group != NULL)
556       dumpTreeInfo(node->node_group, depth + 1);
557
558     node = node->next;
559   }
560 }
561
562 void sortTreeInfo(TreeInfo **node_first,
563                   int (*compare_function)(const void *, const void *))
564 {
565   int num_nodes = numTreeInfo(*node_first);
566   TreeInfo **sort_array;
567   TreeInfo *node = *node_first;
568   int i = 0;
569
570   if (num_nodes == 0)
571     return;
572
573   /* allocate array for sorting structure pointers */
574   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
575
576   /* writing structure pointers to sorting array */
577   while (i < num_nodes && node)         /* double boundary check... */
578   {
579     sort_array[i] = node;
580
581     i++;
582     node = node->next;
583   }
584
585   /* sorting the structure pointers in the sorting array */
586   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
587         compare_function);
588
589   /* update the linkage of list elements with the sorted node array */
590   for (i=0; i<num_nodes - 1; i++)
591     sort_array[i]->next = sort_array[i + 1];
592   sort_array[num_nodes - 1]->next = NULL;
593
594   /* update the linkage of the main list anchor pointer */
595   *node_first = sort_array[0];
596
597   free(sort_array);
598
599   /* now recursively sort the level group structures */
600   node = *node_first;
601   while (node)
602   {
603     if (node->node_group != NULL)
604       sortTreeInfo(&node->node_group, compare_function);
605
606     node = node->next;
607   }
608 }
609
610
611 /* ========================================================================= */
612 /* some stuff from "files.c"                                                 */
613 /* ========================================================================= */
614
615 #if defined(PLATFORM_WIN32)
616 #ifndef S_IRGRP
617 #define S_IRGRP S_IRUSR
618 #endif
619 #ifndef S_IROTH
620 #define S_IROTH S_IRUSR
621 #endif
622 #ifndef S_IWGRP
623 #define S_IWGRP S_IWUSR
624 #endif
625 #ifndef S_IWOTH
626 #define S_IWOTH S_IWUSR
627 #endif
628 #ifndef S_IXGRP
629 #define S_IXGRP S_IXUSR
630 #endif
631 #ifndef S_IXOTH
632 #define S_IXOTH S_IXUSR
633 #endif
634 #ifndef S_IRWXG
635 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
636 #endif
637 #ifndef S_ISGID
638 #define S_ISGID 0
639 #endif
640 #endif  /* PLATFORM_WIN32 */
641
642 /* file permissions for newly written files */
643 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
644 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
645 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
646
647 #define MODE_W_PRIVATE          (S_IWUSR)
648 #define MODE_W_PUBLIC           (S_IWUSR | S_IWGRP)
649 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
650
651 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
652 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
653
654 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
655 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC)
656
657 char *getUserDataDir(void)
658 {
659   static char *userdata_dir = NULL;
660
661   if (!userdata_dir)
662   {
663     char *home_dir = getHomeDir();
664     char *data_dir = program.userdata_directory;
665
666     userdata_dir = getPath2(home_dir, data_dir);
667   }
668
669   return userdata_dir;
670 }
671
672 char *getSetupDir()
673 {
674   return getUserDataDir();
675 }
676
677 static mode_t posix_umask(mode_t mask)
678 {
679 #if defined(PLATFORM_UNIX)
680   return umask(mask);
681 #else
682   return 0;
683 #endif
684 }
685
686 static int posix_mkdir(const char *pathname, mode_t mode)
687 {
688 #if defined(PLATFORM_WIN32)
689   return mkdir(pathname);
690 #else
691   return mkdir(pathname, mode);
692 #endif
693 }
694
695 void createDirectory(char *dir, char *text, int permission_class)
696 {
697   /* leave "other" permissions in umask untouched, but ensure group parts
698      of USERDATA_DIR_MODE are not masked */
699   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
700                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
701   mode_t normal_umask = posix_umask(0);
702   mode_t group_umask = ~(dir_mode & S_IRWXG);
703   posix_umask(normal_umask & group_umask);
704
705   if (access(dir, F_OK) != 0)
706     if (posix_mkdir(dir, dir_mode) != 0)
707       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
708
709   posix_umask(normal_umask);            /* reset normal umask */
710 }
711
712 void InitUserDataDirectory()
713 {
714   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
715 }
716
717 void SetFilePermissions(char *filename, int permission_class)
718 {
719   chmod(filename, (permission_class == PERMS_PRIVATE ?
720                    FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
721 }
722
723 char *getCookie(char *file_type)
724 {
725   static char cookie[MAX_COOKIE_LEN + 1];
726
727   if (strlen(program.cookie_prefix) + 1 +
728       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
729     return "[COOKIE ERROR]";    /* should never happen */
730
731   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
732           program.cookie_prefix, file_type,
733           program.version_major, program.version_minor);
734
735   return cookie;
736 }
737
738 int getFileVersionFromCookieString(const char *cookie)
739 {
740   const char *ptr_cookie1, *ptr_cookie2;
741   const char *pattern1 = "_FILE_VERSION_";
742   const char *pattern2 = "?.?";
743   const int len_cookie = strlen(cookie);
744   const int len_pattern1 = strlen(pattern1);
745   const int len_pattern2 = strlen(pattern2);
746   const int len_pattern = len_pattern1 + len_pattern2;
747   int version_major, version_minor;
748
749   if (len_cookie <= len_pattern)
750     return -1;
751
752   ptr_cookie1 = &cookie[len_cookie - len_pattern];
753   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
754
755   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
756     return -1;
757
758   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
759       ptr_cookie2[1] != '.' ||
760       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
761     return -1;
762
763   version_major = ptr_cookie2[0] - '0';
764   version_minor = ptr_cookie2[2] - '0';
765
766   return VERSION_IDENT(version_major, version_minor, 0);
767 }
768
769 boolean checkCookieString(const char *cookie, const char *template)
770 {
771   const char *pattern = "_FILE_VERSION_?.?";
772   const int len_cookie = strlen(cookie);
773   const int len_template = strlen(template);
774   const int len_pattern = strlen(pattern);
775
776   if (len_cookie != len_template)
777     return FALSE;
778
779   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
780     return FALSE;
781
782   return TRUE;
783 }
784
785 /* ------------------------------------------------------------------------- */
786 /* setup file list handling functions                                        */
787 /* ------------------------------------------------------------------------- */
788
789 int get_string_integer_value(char *s)
790 {
791   static char *number_text[][3] =
792   {
793     { "0", "zero", "null", },
794     { "1", "one", "first" },
795     { "2", "two", "second" },
796     { "3", "three", "third" },
797     { "4", "four", "fourth" },
798     { "5", "five", "fifth" },
799     { "6", "six", "sixth" },
800     { "7", "seven", "seventh" },
801     { "8", "eight", "eighth" },
802     { "9", "nine", "ninth" },
803     { "10", "ten", "tenth" },
804     { "11", "eleven", "eleventh" },
805     { "12", "twelve", "twelfth" },
806   };
807
808   int i, j;
809   char *s_lower = getStringToLower(s);
810   int result = -1;
811
812   for (i=0; i<13; i++)
813     for (j=0; j<3; j++)
814       if (strcmp(s_lower, number_text[i][j]) == 0)
815         result = i;
816
817   if (result == -1)
818     result = atoi(s);
819
820   free(s_lower);
821
822   return result;
823 }
824
825 boolean get_string_boolean_value(char *s)
826 {
827   char *s_lower = getStringToLower(s);
828   boolean result = FALSE;
829
830   if (strcmp(s_lower, "true") == 0 ||
831       strcmp(s_lower, "yes") == 0 ||
832       strcmp(s_lower, "on") == 0 ||
833       get_string_integer_value(s) == 1)
834     result = TRUE;
835
836   free(s_lower);
837
838   return result;
839 }
840
841 char *getFormattedSetupEntry(char *token, char *value)
842 {
843   int i;
844   static char entry[MAX_LINE_LEN];
845
846   sprintf(entry, "%s:", token);
847   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
848     entry[i] = ' ';
849   entry[i] = '\0';
850
851   strcat(entry, value);
852
853   return entry;
854 }
855
856 void freeSetupFileList(struct SetupFileList *setup_file_list)
857 {
858   if (!setup_file_list)
859     return;
860
861   if (setup_file_list->token)
862     free(setup_file_list->token);
863   if (setup_file_list->value)
864     free(setup_file_list->value);
865   if (setup_file_list->next)
866     freeSetupFileList(setup_file_list->next);
867   free(setup_file_list);
868 }
869
870 static struct SetupFileList *newSetupFileList(char *token, char *value)
871 {
872   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
873
874   new->token = checked_malloc(strlen(token) + 1);
875   strcpy(new->token, token);
876
877   new->value = checked_malloc(strlen(value) + 1);
878   strcpy(new->value, value);
879
880   new->next = NULL;
881
882   return new;
883 }
884
885 char *getTokenValue(struct SetupFileList *setup_file_list, char *token)
886 {
887   if (!setup_file_list)
888     return NULL;
889
890   if (strcmp(setup_file_list->token, token) == 0)
891     return setup_file_list->value;
892   else
893     return getTokenValue(setup_file_list->next, token);
894 }
895
896 static void setTokenValue(struct SetupFileList *setup_file_list,
897                           char *token, char *value)
898 {
899   if (!setup_file_list)
900     return;
901
902   if (strcmp(setup_file_list->token, token) == 0)
903   {
904     free(setup_file_list->value);
905     setup_file_list->value = checked_malloc(strlen(value) + 1);
906     strcpy(setup_file_list->value, value);
907   }
908   else if (setup_file_list->next == NULL)
909     setup_file_list->next = newSetupFileList(token, value);
910   else
911     setTokenValue(setup_file_list->next, token, value);
912 }
913
914 #ifdef DEBUG
915 static void printSetupFileList(struct SetupFileList *setup_file_list)
916 {
917   if (!setup_file_list)
918     return;
919
920   printf("token: '%s'\n", setup_file_list->token);
921   printf("value: '%s'\n", setup_file_list->value);
922
923   printSetupFileList(setup_file_list->next);
924 }
925 #endif
926
927 struct SetupFileList *loadSetupFileList(char *filename)
928 {
929   int line_len;
930   char line[MAX_LINE_LEN];
931   char *token, *value, *line_ptr;
932   struct SetupFileList *setup_file_list = newSetupFileList("", "");
933   struct SetupFileList *first_valid_list_entry;
934
935   FILE *file;
936
937   if (!(file = fopen(filename, MODE_READ)))
938   {
939     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
940     return NULL;
941   }
942
943   while(!feof(file))
944   {
945     /* read next line of input file */
946     if (!fgets(line, MAX_LINE_LEN, file))
947       break;
948
949     /* cut trailing comment or whitespace from input line */
950     for (line_ptr = line; *line_ptr; line_ptr++)
951     {
952       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
953       {
954         *line_ptr = '\0';
955         break;
956       }
957     }
958
959     /* cut trailing whitespaces from input line */
960     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
961       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
962         *line_ptr = '\0';
963
964     /* ignore empty lines */
965     if (*line == '\0')
966       continue;
967
968     line_len = strlen(line);
969
970     /* cut leading whitespaces from token */
971     for (token = line; *token; token++)
972       if (*token != ' ' && *token != '\t')
973         break;
974
975     /* find end of token */
976     for (line_ptr = token; *line_ptr; line_ptr++)
977     {
978       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
979       {
980         *line_ptr = '\0';
981         break;
982       }
983     }
984
985     if (line_ptr < line + line_len)
986       value = line_ptr + 1;
987     else
988       value = "\0";
989
990     /* cut leading whitespaces from value */
991     for (; *value; value++)
992       if (*value != ' ' && *value != '\t')
993         break;
994
995     if (*token && *value)
996       setTokenValue(setup_file_list, token, value);
997   }
998
999   fclose(file);
1000
1001   first_valid_list_entry = setup_file_list->next;
1002
1003   /* free empty list header */
1004   setup_file_list->next = NULL;
1005   freeSetupFileList(setup_file_list);
1006
1007   if (first_valid_list_entry == NULL)
1008     Error(ERR_WARN, "configuration file '%s' is empty", filename);
1009
1010   return first_valid_list_entry;
1011 }
1012
1013 void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
1014                                   char *identifier)
1015 {
1016   if (!setup_file_list)
1017     return;
1018
1019   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
1020   {
1021     if (!checkCookieString(setup_file_list->value, identifier))
1022     {
1023       Error(ERR_WARN, "configuration file has wrong file identifier");
1024       return;
1025     }
1026     else
1027       return;
1028   }
1029
1030   if (setup_file_list->next)
1031     checkSetupFileListIdentifier(setup_file_list->next, identifier);
1032   else
1033   {
1034     Error(ERR_WARN, "configuration file has no file identifier");
1035     return;
1036   }
1037 }
1038
1039
1040 /* ========================================================================= */
1041 /* setup file stuff                                                          */
1042 /* ========================================================================= */
1043
1044 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1045 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1046 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1047
1048 /* level directory info */
1049 #define LEVELINFO_TOKEN_NAME            0
1050 #define LEVELINFO_TOKEN_NAME_SHORT      1
1051 #define LEVELINFO_TOKEN_NAME_SORTING    2
1052 #define LEVELINFO_TOKEN_AUTHOR          3
1053 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1054 #define LEVELINFO_TOKEN_LEVELS          5
1055 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
1056 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
1057 #define LEVELINFO_TOKEN_LEVEL_GROUP     8
1058 #define LEVELINFO_TOKEN_READONLY        9
1059
1060 #define NUM_LEVELINFO_TOKENS            10
1061
1062 static LevelDirTree ldi;
1063
1064 static struct TokenInfo levelinfo_tokens[] =
1065 {
1066   /* level directory info */
1067   { TYPE_STRING,  &ldi.name,            "name"          },
1068   { TYPE_STRING,  &ldi.name_short,      "name_short"    },
1069   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1070   { TYPE_STRING,  &ldi.author,          "author"        },
1071   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1072   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1073   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1074   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1075   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1076   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      }
1077 };
1078
1079 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1080 {
1081   ldi->type = type;
1082
1083   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1084                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1085                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1086                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1087                    NULL);
1088
1089   ldi->node_parent = NULL;
1090   ldi->node_group = NULL;
1091   ldi->next = NULL;
1092
1093   ldi->cl_first = -1;
1094   ldi->cl_cursor = -1;
1095
1096   ldi->filename = NULL;
1097   ldi->fullpath = NULL;
1098   ldi->basepath = NULL;
1099   ldi->name = getStringCopy(ANONYMOUS_NAME);
1100   ldi->name_short = NULL;
1101   ldi->name_sorting = NULL;
1102   ldi->author = getStringCopy(ANONYMOUS_NAME);
1103
1104   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1105   ldi->parent_link = FALSE;
1106   ldi->user_defined = FALSE;
1107   ldi->color = 0;
1108   ldi->class_desc = NULL;
1109
1110   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1111   {
1112     ldi->imported_from = NULL;
1113     ldi->levels = 0;
1114     ldi->first_level = 0;
1115     ldi->last_level = 0;
1116     ldi->level_group = FALSE;
1117     ldi->handicap_level = 0;
1118     ldi->readonly = TRUE;
1119   }
1120 }
1121
1122 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1123 {
1124   if (parent == NULL)
1125   {
1126     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1127
1128     setTreeInfoToDefaults(ldi, TREE_TYPE_GENERIC);
1129     return;
1130   }
1131
1132   /* first copy all values from the parent structure ... */
1133   *ldi = *parent;
1134
1135   /* ... then set all fields to default that cannot be inherited from parent.
1136      This is especially important for all those fields that can be set from
1137      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1138      calls 'free()' for all already set token values which requires that no
1139      other structure's pointer may point to them!
1140   */
1141
1142   ldi->filename = NULL;
1143   ldi->fullpath = NULL;
1144   ldi->basepath = NULL;
1145   ldi->name = getStringCopy(ANONYMOUS_NAME);
1146   ldi->name_short = NULL;
1147   ldi->name_sorting = NULL;
1148   ldi->author = getStringCopy(parent->author);
1149   ldi->imported_from = getStringCopy(parent->imported_from);
1150
1151   ldi->level_group = FALSE;
1152   ldi->parent_link = FALSE;
1153
1154   ldi->node_top = parent->node_top;
1155   ldi->node_parent = parent;
1156   ldi->node_group = NULL;
1157   ldi->next = NULL;
1158 }
1159
1160 void setSetupInfo(struct TokenInfo *token_info,
1161                   int token_nr, char *token_value)
1162 {
1163   int token_type = token_info[token_nr].type;
1164   void *setup_value = token_info[token_nr].value;
1165
1166   if (token_value == NULL)
1167     return;
1168
1169   /* set setup field to corresponding token value */
1170   switch (token_type)
1171   {
1172     case TYPE_BOOLEAN:
1173     case TYPE_SWITCH:
1174       *(boolean *)setup_value = get_string_boolean_value(token_value);
1175       break;
1176
1177     case TYPE_KEY:
1178       *(Key *)setup_value = getKeyFromKeyName(token_value);
1179       break;
1180
1181     case TYPE_KEY_X11:
1182       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1183       break;
1184
1185     case TYPE_INTEGER:
1186       *(int *)setup_value = get_string_integer_value(token_value);
1187       break;
1188
1189     case TYPE_STRING:
1190       if (*(char **)setup_value != NULL)
1191         free(*(char **)setup_value);
1192       *(char **)setup_value = getStringCopy(token_value);
1193       break;
1194
1195     default:
1196       break;
1197   }
1198 }
1199
1200 static int compareTreeInfoEntries(const void *object1, const void *object2)
1201 {
1202   const TreeInfo *entry1 = *((TreeInfo **)object1);
1203   const TreeInfo *entry2 = *((TreeInfo **)object2);
1204   int compare_result;
1205
1206   if (entry1->parent_link || entry2->parent_link)
1207     compare_result = (entry1->parent_link ? -1 : +1);
1208   else if (entry1->sort_priority == entry2->sort_priority)
1209   {
1210     char *name1 = getStringToLower(entry1->name_sorting);
1211     char *name2 = getStringToLower(entry2->name_sorting);
1212
1213     compare_result = strcmp(name1, name2);
1214
1215     free(name1);
1216     free(name2);
1217   }
1218   else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1219     compare_result = entry1->sort_priority - entry2->sort_priority;
1220   else
1221     compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1222
1223   return compare_result;
1224 }
1225
1226 static void createParentTreeInfoNode(TreeInfo *node_parent)
1227 {
1228   TreeInfo *ti_new;
1229
1230   if (node_parent == NULL)
1231     return;
1232
1233   ti_new = newTreeInfo();
1234   setTreeInfoToDefaults(ti_new, node_parent->type);
1235
1236   ti_new->node_parent = node_parent;
1237   ti_new->parent_link = TRUE;
1238
1239   ti_new->name = ".. (parent directory)";
1240   ti_new->name_short = getStringCopy(ti_new->name);
1241   ti_new->name_sorting = getStringCopy(ti_new->name);
1242
1243   ti_new->filename = "..";
1244   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1245
1246   ti_new->sort_priority = node_parent->sort_priority;
1247   ti_new->class_desc = getLevelClassDescription(ti_new);
1248
1249   pushTreeInfo(&node_parent->node_group, ti_new);
1250 }
1251
1252 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1253 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1254
1255 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1256                                           TreeInfo *node_parent,
1257                                           char *level_directory,
1258                                           char *directory_name)
1259 {
1260   char *directory_path = getPath2(level_directory, directory_name);
1261   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1262   struct SetupFileList *setup_file_list = loadSetupFileList(filename);
1263   LevelDirTree *leveldir_new = NULL;
1264   int i;
1265
1266   if (setup_file_list == NULL)
1267   {
1268     Error(ERR_WARN, "ignoring level directory '%s'", level_directory);
1269
1270     free(directory_path);
1271     free(filename);
1272
1273     return FALSE;
1274   }
1275
1276   leveldir_new = newTreeInfo();
1277
1278   if (node_parent)
1279     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1280   else
1281     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1282
1283   checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1284
1285   /* set all structure fields according to the token/value pairs */
1286   ldi = *leveldir_new;
1287   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1288     setSetupInfo(levelinfo_tokens, i,
1289                  getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1290   *leveldir_new = ldi;
1291
1292   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1293
1294   if (leveldir_new->name_short == NULL)
1295     leveldir_new->name_short = getStringCopy(leveldir_new->name);
1296
1297   if (leveldir_new->name_sorting == NULL)
1298     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1299
1300   leveldir_new->filename = getStringCopy(directory_name);
1301
1302   if (node_parent == NULL)              /* top level group */
1303   {
1304     leveldir_new->basepath = level_directory;
1305     leveldir_new->fullpath = leveldir_new->filename;
1306   }
1307   else                                  /* sub level group */
1308   {
1309     leveldir_new->basepath = node_parent->basepath;
1310     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1311   }
1312
1313   if (leveldir_new->levels < 1)
1314     leveldir_new->levels = 1;
1315
1316   leveldir_new->last_level =
1317     leveldir_new->first_level + leveldir_new->levels - 1;
1318
1319   leveldir_new->user_defined =
1320     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1321
1322   leveldir_new->color = LEVELCOLOR(leveldir_new);
1323   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1324
1325   leveldir_new->handicap_level =        /* set handicap to default value */
1326     (leveldir_new->user_defined ?
1327      leveldir_new->last_level :
1328      leveldir_new->first_level);
1329
1330   pushTreeInfo(node_first, leveldir_new);
1331
1332   freeSetupFileList(setup_file_list);
1333
1334   if (leveldir_new->level_group)
1335   {
1336     /* create node to link back to current level directory */
1337     createParentTreeInfoNode(leveldir_new);
1338
1339     /* step into sub-directory and look for more level series */
1340     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1341                               leveldir_new, directory_path);
1342   }
1343
1344   free(directory_path);
1345   free(filename);
1346
1347   return TRUE;
1348 }
1349
1350 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1351                                       TreeInfo *node_parent,
1352                                       char *level_directory)
1353 {
1354   DIR *dir;
1355   struct dirent *dir_entry;
1356   boolean valid_entry_found = FALSE;
1357
1358   if ((dir = opendir(level_directory)) == NULL)
1359   {
1360     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1361     return;
1362   }
1363
1364   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1365   {
1366     struct stat file_status;
1367     char *directory_name = dir_entry->d_name;
1368     char *directory_path = getPath2(level_directory, directory_name);
1369
1370     /* skip entries for current and parent directory */
1371     if (strcmp(directory_name, ".")  == 0 ||
1372         strcmp(directory_name, "..") == 0)
1373     {
1374       free(directory_path);
1375       continue;
1376     }
1377
1378     /* find out if directory entry is itself a directory */
1379     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1380         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1381     {
1382       free(directory_path);
1383       continue;
1384     }
1385
1386     free(directory_path);
1387
1388     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1389                                                     level_directory,
1390                                                     directory_name);
1391   }
1392
1393   closedir(dir);
1394
1395   if (!valid_entry_found)
1396   {
1397     /* check if this directory directly contains a file "levelinfo.conf" */
1398     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1399                                                     level_directory, ".");
1400   }
1401
1402   if (!valid_entry_found)
1403     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1404           level_directory);
1405 }
1406
1407 void LoadLevelInfo()
1408 {
1409   InitUserLevelDirectory(getLoginName());
1410
1411   DrawInitText("Loading level series:", 120, FC_GREEN);
1412
1413   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1414   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1415
1416   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1417
1418   if (leveldir_first == NULL)
1419     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1420
1421   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1422
1423 #if 0
1424   dumpTreeInfo(leveldir_first, 0);
1425 #endif
1426 }
1427
1428 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1429                                               TreeInfo *node_parent,
1430                                               char *base_directory,
1431                                               char *directory_name, int type)
1432 {
1433   char *directory_path = getPath2(base_directory, directory_name);
1434   char *filename =
1435     getPath2(directory_path,
1436              (type == TREE_TYPE_GRAPHICS_DIR ? GRAPHICSINFO_FILENAME :
1437               type == TREE_TYPE_SOUNDS_DIR ? SOUNDSINFO_FILENAME :
1438               type == TREE_TYPE_MUSIC_DIR ? MUSICINFO_FILENAME : ""));
1439   struct SetupFileList *setup_file_list = NULL;
1440   TreeInfo *artwork_new = NULL;
1441   char *check_dir = NULL;
1442   int i;
1443
1444   if (access(getUserLevelDir(filename), F_OK) == 0)     /* file exists */
1445     loadSetupFileList(filename);
1446
1447   if (setup_file_list == NULL)  /* no config file -- look for artwork files */
1448   {
1449     DIR *dir;
1450     struct dirent *dir_entry;
1451     boolean valid_file_found = FALSE;
1452
1453     if ((dir = opendir(base_directory)) != NULL)
1454     {
1455       while ((dir_entry = readdir(dir)) != NULL)
1456       {
1457         char *entry_name = dir_entry->d_name;
1458
1459         if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(entry_name)) ||
1460             (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(entry_name)) ||
1461             (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(entry_name)))
1462         {
1463           valid_file_found = TRUE;
1464           break;
1465         }
1466       }
1467
1468       closedir(dir);
1469     }
1470
1471     if (!valid_file_found)
1472     {
1473       Error(ERR_WARN, "ignoring artwork directory '%s'", base_directory);
1474
1475       free(directory_path);
1476       free(filename);
1477
1478       return FALSE;
1479     }
1480   }
1481
1482   artwork_new = newTreeInfo();
1483
1484   if (node_parent)
1485     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1486   else
1487     setTreeInfoToDefaults(artwork_new, type);
1488
1489   artwork_new->filename = getStringCopy(directory_name);
1490
1491   if (setup_file_list)  /* (before defining ".color" and ".class_desc") */
1492   {
1493 #if 0
1494     checkSetupFileListIdentifier(setup_file_list, getCookie("..."));
1495 #endif
1496
1497     /* set all structure fields according to the token/value pairs */
1498     ldi = *artwork_new;
1499     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1500       setSetupInfo(levelinfo_tokens, i,
1501                    getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1502     *artwork_new = ldi;
1503
1504     DrawInitText(artwork_new->name, 150, FC_YELLOW);
1505
1506     if (artwork_new->name_short == NULL)
1507       artwork_new->name_short = getStringCopy(artwork_new->name);
1508
1509     if (artwork_new->name_sorting == NULL)
1510       artwork_new->name_sorting = getStringCopy(artwork_new->name);
1511   }
1512
1513   if (node_parent == NULL)              /* top level group */
1514   {
1515     artwork_new->basepath = base_directory;
1516     artwork_new->fullpath = artwork_new->filename;
1517   }
1518   else                                  /* sub level group */
1519   {
1520     artwork_new->basepath = node_parent->basepath;
1521     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1522   }
1523
1524   check_dir = (type == TREE_TYPE_GRAPHICS_DIR ? options.graphics_directory :
1525                type == TREE_TYPE_SOUNDS_DIR ? options.sounds_directory :
1526                type == TREE_TYPE_MUSIC_DIR ? options.music_directory : "");
1527   artwork_new->user_defined =
1528     (artwork_new->basepath == check_dir ? FALSE : TRUE);
1529
1530   /* (may use ".sort_priority" from "setup_file_list" above) */
1531   artwork_new->color = LEVELCOLOR(artwork_new);
1532   artwork_new->class_desc = getLevelClassDescription(artwork_new);
1533
1534   if (setup_file_list == NULL)  /* (after determining ".user_defined") */
1535   {
1536     if (artwork_new->name != NULL)
1537       free(artwork_new->name);
1538
1539     if (strcmp(artwork_new->filename, ".") == 0)
1540     {
1541       if (artwork_new->user_defined)
1542         artwork_new->name = getStringCopy("private");
1543       else
1544         artwork_new->name = getStringCopy("default");
1545     }
1546     else
1547       artwork_new->name = getStringCopy(artwork_new->filename);
1548
1549     artwork_new->name_short = getStringCopy(artwork_new->name);
1550     artwork_new->name_sorting = getStringCopy(artwork_new->name);
1551   }
1552
1553   pushTreeInfo(node_first, artwork_new);
1554
1555   freeSetupFileList(setup_file_list);
1556
1557   free(directory_path);
1558   free(filename);
1559
1560   return TRUE;
1561 }
1562
1563 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
1564                                           TreeInfo *node_parent,
1565                                           char *base_directory, int type)
1566 {
1567   DIR *dir;
1568   struct dirent *dir_entry;
1569   boolean valid_entry_found = FALSE;
1570
1571   if ((dir = opendir(base_directory)) == NULL)
1572   {
1573     if ((type == TREE_TYPE_GRAPHICS_DIR &&
1574          base_directory == options.graphics_directory) ||
1575         (type == TREE_TYPE_SOUNDS_DIR &&
1576          base_directory == options.sounds_directory) ||
1577         (type == TREE_TYPE_MUSIC_DIR &&
1578          base_directory == options.music_directory))
1579       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
1580     return;
1581   }
1582
1583   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1584   {
1585     struct stat file_status;
1586     char *directory_name = dir_entry->d_name;
1587     char *directory_path = getPath2(base_directory, directory_name);
1588
1589     /* skip entries for current and parent directory */
1590     if (strcmp(directory_name, ".")  == 0 ||
1591         strcmp(directory_name, "..") == 0)
1592     {
1593       free(directory_path);
1594       continue;
1595     }
1596
1597     /* find out if directory entry is itself a directory */
1598     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1599         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1600     {
1601       free(directory_path);
1602       continue;
1603     }
1604
1605     free(directory_path);
1606
1607     /* check if this directory contains artwork with or without config file */
1608     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1609                                                         base_directory,
1610                                                         directory_name, type);
1611   }
1612
1613   closedir(dir);
1614
1615   /* check if this directory directly contains artwork itself */
1616   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1617                                                       base_directory, ".",
1618                                                       type);
1619   if (!valid_entry_found)
1620     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
1621           base_directory);
1622 }
1623
1624 void LoadArtworkInfo()
1625 {
1626   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
1627
1628   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1629                                 options.graphics_directory,
1630                                 TREE_TYPE_GRAPHICS_DIR);
1631   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1632                                 getUserGraphicsDir(NULL),
1633                                 TREE_TYPE_GRAPHICS_DIR);
1634
1635   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1636                                 options.sounds_directory,
1637                                 TREE_TYPE_SOUNDS_DIR);
1638   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1639                                 getUserSoundsDir(NULL),
1640                                 TREE_TYPE_SOUNDS_DIR);
1641
1642   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1643                                 options.music_directory,
1644                                 TREE_TYPE_MUSIC_DIR);
1645   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1646                                 getUserMusicDir(NULL),
1647                                 TREE_TYPE_MUSIC_DIR);
1648
1649   /* before sorting, the first entries will be from the user directory */
1650   artwork.gfx_current = artwork.gfx_first;
1651   artwork.snd_current = artwork.snd_first;
1652   artwork.mus_current = artwork.mus_first;
1653
1654   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
1655   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
1656   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
1657
1658 #if 1
1659   dumpTreeInfo(artwork.gfx_first, 0);
1660   dumpTreeInfo(artwork.snd_first, 0);
1661   dumpTreeInfo(artwork.mus_first, 0);
1662 #endif
1663 }
1664
1665 static void SaveUserLevelInfo()
1666 {
1667   char *filename;
1668   FILE *file;
1669   int i;
1670
1671   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1672
1673   if (!(file = fopen(filename, MODE_WRITE)))
1674   {
1675     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1676     free(filename);
1677     return;
1678   }
1679
1680   /* always start with reliable default values */
1681   setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
1682
1683   ldi.name = getLoginName();
1684   ldi.author = getRealName();
1685   ldi.levels = 100;
1686   ldi.first_level = 1;
1687   ldi.sort_priority = LEVELCLASS_USER_START;
1688   ldi.readonly = FALSE;
1689
1690   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1691                                                  getCookie("LEVELINFO")));
1692
1693   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1694     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1695         i != LEVELINFO_TOKEN_NAME_SORTING &&
1696         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1697       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1698
1699   fclose(file);
1700   free(filename);
1701
1702   SetFilePermissions(filename, PERMS_PRIVATE);
1703 }
1704
1705 char *getSetupValue(int type, void *value)
1706 {
1707   static char value_string[MAX_LINE_LEN];
1708
1709   switch (type)
1710   {
1711     case TYPE_BOOLEAN:
1712       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
1713       break;
1714
1715     case TYPE_SWITCH:
1716       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
1717       break;
1718
1719     case TYPE_YES_NO:
1720       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
1721       break;
1722
1723     case TYPE_KEY:
1724       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
1725       break;
1726
1727     case TYPE_KEY_X11:
1728       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
1729       break;
1730
1731     case TYPE_INTEGER:
1732       sprintf(value_string, "%d", *(int *)value);
1733       break;
1734
1735     case TYPE_STRING:
1736       strcpy(value_string, *(char **)value);
1737       break;
1738
1739     default:
1740       value_string[0] = '\0';
1741       break;
1742   }
1743
1744   return value_string;
1745 }
1746
1747 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1748 {
1749   int i;
1750   static char entry[MAX_LINE_LEN];
1751   int token_type = token_info[token_nr].type;
1752   void *setup_value = token_info[token_nr].value;
1753   char *token_text = token_info[token_nr].text;
1754   char *value_string = getSetupValue(token_type, setup_value);
1755
1756   /* start with the prefix, token and some spaces to format output line */
1757   sprintf(entry, "%s%s:", prefix, token_text);
1758   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1759     strcat(entry, " ");
1760
1761   /* continue with the token's value (which can have different types) */
1762   strcat(entry, value_string);
1763
1764   if (token_type == TYPE_KEY_X11)
1765   {
1766     Key key = *(Key *)setup_value;
1767     char *keyname = getKeyNameFromKey(key);
1768
1769     /* add comment, if useful */
1770     if (strcmp(keyname, "(undefined)") != 0 &&
1771         strcmp(keyname, "(unknown)") != 0)
1772     {
1773       for (i=strlen(entry); i<50; i++)
1774         strcat(entry, " ");
1775
1776       strcat(entry, "# ");
1777       strcat(entry, keyname);
1778     }
1779   }
1780
1781   return entry;
1782 }
1783
1784 void LoadLevelSetup_LastSeries()
1785 {
1786   char *filename;
1787   struct SetupFileList *level_setup_list = NULL;
1788
1789   /* always start with reliable default values */
1790   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1791
1792   /* ----------------------------------------------------------------------- */
1793   /* ~/.<program>/levelsetup.conf                                            */
1794   /* ----------------------------------------------------------------------- */
1795
1796   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1797
1798   if ((level_setup_list = loadSetupFileList(filename)))
1799   {
1800     char *last_level_series =
1801       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1802
1803     leveldir_current = getTreeInfoFromFilename(leveldir_first,
1804                                                last_level_series);
1805     if (leveldir_current == NULL)
1806       leveldir_current = leveldir_first;
1807
1808     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1809
1810     freeSetupFileList(level_setup_list);
1811   }
1812   else
1813     Error(ERR_WARN, "using default setup values");
1814
1815   free(filename);
1816 }
1817
1818 void SaveLevelSetup_LastSeries()
1819 {
1820   char *filename;
1821   char *level_subdir = leveldir_current->filename;
1822   FILE *file;
1823
1824   /* ----------------------------------------------------------------------- */
1825   /* ~/.<program>/levelsetup.conf                                            */
1826   /* ----------------------------------------------------------------------- */
1827
1828   InitUserDataDirectory();
1829
1830   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1831
1832   if (!(file = fopen(filename, MODE_WRITE)))
1833   {
1834     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1835     free(filename);
1836     return;
1837   }
1838
1839   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1840                                                  getCookie("LEVELSETUP")));
1841   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1842                                                level_subdir));
1843
1844   fclose(file);
1845   free(filename);
1846
1847   SetFilePermissions(filename, PERMS_PRIVATE);
1848 }
1849
1850 static void checkSeriesInfo()
1851 {
1852   static char *level_directory = NULL;
1853   DIR *dir;
1854   struct dirent *dir_entry;
1855
1856   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1857
1858   level_directory = getPath2((leveldir_current->user_defined ?
1859                               getUserLevelDir(NULL) :
1860                               options.level_directory),
1861                              leveldir_current->fullpath);
1862
1863   if ((dir = opendir(level_directory)) == NULL)
1864   {
1865     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1866     return;
1867   }
1868
1869   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
1870   {
1871     if (strlen(dir_entry->d_name) > 4 &&
1872         dir_entry->d_name[3] == '.' &&
1873         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1874     {
1875       char levelnum_str[4];
1876       int levelnum_value;
1877
1878       strncpy(levelnum_str, dir_entry->d_name, 3);
1879       levelnum_str[3] = '\0';
1880
1881       levelnum_value = atoi(levelnum_str);
1882
1883       if (levelnum_value < leveldir_current->first_level)
1884       {
1885         Error(ERR_WARN, "additional level %d found", levelnum_value);
1886         leveldir_current->first_level = levelnum_value;
1887       }
1888       else if (levelnum_value > leveldir_current->last_level)
1889       {
1890         Error(ERR_WARN, "additional level %d found", levelnum_value);
1891         leveldir_current->last_level = levelnum_value;
1892       }
1893     }
1894   }
1895
1896   closedir(dir);
1897 }
1898
1899 void LoadLevelSetup_SeriesInfo()
1900 {
1901   char *filename;
1902   struct SetupFileList *level_setup_list = NULL;
1903   char *level_subdir = leveldir_current->filename;
1904
1905   /* always start with reliable default values */
1906   level_nr = leveldir_current->first_level;
1907
1908   checkSeriesInfo(leveldir_current);
1909
1910   /* ----------------------------------------------------------------------- */
1911   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1912   /* ----------------------------------------------------------------------- */
1913
1914   level_subdir = leveldir_current->filename;
1915
1916   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1917
1918   if ((level_setup_list = loadSetupFileList(filename)))
1919   {
1920     char *token_value;
1921
1922     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1923
1924     if (token_value)
1925     {
1926       level_nr = atoi(token_value);
1927
1928       if (level_nr < leveldir_current->first_level)
1929         level_nr = leveldir_current->first_level;
1930       if (level_nr > leveldir_current->last_level)
1931         level_nr = leveldir_current->last_level;
1932     }
1933
1934     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
1935
1936     if (token_value)
1937     {
1938       int level_nr = atoi(token_value);
1939
1940       if (level_nr < leveldir_current->first_level)
1941         level_nr = leveldir_current->first_level;
1942       if (level_nr > leveldir_current->last_level + 1)
1943         level_nr = leveldir_current->last_level;
1944
1945       if (leveldir_current->user_defined)
1946         level_nr = leveldir_current->last_level;
1947
1948       leveldir_current->handicap_level = level_nr;
1949     }
1950
1951     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1952
1953     freeSetupFileList(level_setup_list);
1954   }
1955   else
1956     Error(ERR_WARN, "using default setup values");
1957
1958   free(filename);
1959 }
1960
1961 void SaveLevelSetup_SeriesInfo()
1962 {
1963   char *filename;
1964   char *level_subdir = leveldir_current->filename;
1965   char *level_nr_str = int2str(level_nr, 0);
1966   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
1967   FILE *file;
1968
1969   /* ----------------------------------------------------------------------- */
1970   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1971   /* ----------------------------------------------------------------------- */
1972
1973   InitLevelSetupDirectory(level_subdir);
1974
1975   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1976
1977   if (!(file = fopen(filename, MODE_WRITE)))
1978   {
1979     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1980     free(filename);
1981     return;
1982   }
1983
1984   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1985                                                  getCookie("LEVELSETUP")));
1986   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
1987                                                level_nr_str));
1988   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
1989                                                handicap_level_str));
1990
1991   fclose(file);
1992   free(filename);
1993
1994   SetFilePermissions(filename, PERMS_PRIVATE);
1995 }