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