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