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