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