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