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