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