rnd-20020519-1-src
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <dirent.h>
15 #include <sys/stat.h>
16 #include <string.h>
17 #include <unistd.h>
18
19 #include "setup.h"
20 #include "joystick.h"
21 #include "text.h"
22 #include "misc.h"
23
24 /* file names and filename extensions */
25 #if !defined(PLATFORM_MSDOS)
26 #define LEVELSETUP_DIRECTORY    "levelsetup"
27 #define SETUP_FILENAME          "setup.conf"
28 #define LEVELSETUP_FILENAME     "levelsetup.conf"
29 #define LEVELINFO_FILENAME      "levelinfo.conf"
30 #define GRAPHICSINFO_FILENAME   "graphicsinfo.conf"
31 #define SOUNDSINFO_FILENAME     "soundsinfo.conf"
32 #define MUSICINFO_FILENAME      "musicinfo.conf"
33 #define LEVELFILE_EXTENSION     "level"
34 #define TAPEFILE_EXTENSION      "tape"
35 #define SCOREFILE_EXTENSION     "score"
36 #else
37 #define LEVELSETUP_DIRECTORY    "lvlsetup"
38 #define SETUP_FILENAME          "setup.cnf"
39 #define LEVELSETUP_FILENAME     "lvlsetup.cnf"
40 #define LEVELINFO_FILENAME      "lvlinfo.cnf"
41 #define GRAPHICSINFO_FILENAME   "gfxinfo.cnf"
42 #define SOUNDSINFO_FILENAME     "sndinfo.cnf"
43 #define MUSICINFO_FILENAME      "musinfo.cnf"
44 #define LEVELFILE_EXTENSION     "lvl"
45 #define TAPEFILE_EXTENSION      "tap"
46 #define SCOREFILE_EXTENSION     "sco"
47 #endif
48
49 #define NUM_LEVELCLASS_DESC     8
50 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
51 {
52   "Tutorial Levels",
53   "Classic Originals",
54   "Contributions",
55   "Private Levels",
56   "Boulderdash",
57   "Emerald Mine",
58   "Supaplex",
59   "DX Boulderdash"
60 };
61
62 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE : \
63                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED : \
64                          IS_LEVELCLASS_BD(n) ?                  FC_GREEN : \
65                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW : \
66                          IS_LEVELCLASS_SP(n) ?                  FC_GREEN : \
67                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW : \
68                          IS_LEVELCLASS_CONTRIBUTION(n) ?        FC_GREEN : \
69                          IS_LEVELCLASS_USER(n) ?                FC_RED : \
70                          FC_BLUE)
71
72 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 : \
73                          IS_LEVELCLASS_CLASSICS(n) ?            1 : \
74                          IS_LEVELCLASS_BD(n) ?                  2 : \
75                          IS_LEVELCLASS_EM(n) ?                  3 : \
76                          IS_LEVELCLASS_SP(n) ?                  4 : \
77                          IS_LEVELCLASS_DX(n) ?                  5 : \
78                          IS_LEVELCLASS_CONTRIBUTION(n) ?        6 : \
79                          IS_LEVELCLASS_USER(n) ?                7 : \
80                          9)
81
82 #define TOKEN_VALUE_POSITION            40
83 #define TOKEN_COMMENT_POSITION          60
84
85 #define MAX_COOKIE_LEN                  256
86
87
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   /* start with the token and some spaces to format output line */
988   sprintf(entry, "%s:", token);
989   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
990     strcat(entry, " ");
991
992   /* continue with the token's value */
993   strcat(entry, value);
994
995   return entry;
996 }
997
998 void freeSetupFileList(struct SetupFileList *setup_file_list)
999 {
1000   if (!setup_file_list)
1001     return;
1002
1003   if (setup_file_list->token)
1004     free(setup_file_list->token);
1005   if (setup_file_list->value)
1006     free(setup_file_list->value);
1007   if (setup_file_list->next)
1008     freeSetupFileList(setup_file_list->next);
1009   free(setup_file_list);
1010 }
1011
1012 static struct SetupFileList *newSetupFileList(char *token, char *value)
1013 {
1014   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
1015
1016   new->token = checked_malloc(strlen(token) + 1);
1017   strcpy(new->token, token);
1018
1019   new->value = checked_malloc(strlen(value) + 1);
1020   strcpy(new->value, value);
1021
1022   new->next = NULL;
1023
1024   return new;
1025 }
1026
1027 char *getTokenValue(struct SetupFileList *setup_file_list, char *token)
1028 {
1029   if (!setup_file_list)
1030     return NULL;
1031
1032   if (strcmp(setup_file_list->token, token) == 0)
1033     return setup_file_list->value;
1034   else
1035     return getTokenValue(setup_file_list->next, token);
1036 }
1037
1038 static void setTokenValue(struct SetupFileList *setup_file_list,
1039                           char *token, char *value)
1040 {
1041   if (!setup_file_list)
1042     return;
1043
1044   if (strcmp(setup_file_list->token, token) == 0)
1045   {
1046     free(setup_file_list->value);
1047     setup_file_list->value = checked_malloc(strlen(value) + 1);
1048     strcpy(setup_file_list->value, value);
1049   }
1050   else if (setup_file_list->next == NULL)
1051     setup_file_list->next = newSetupFileList(token, value);
1052   else
1053     setTokenValue(setup_file_list->next, token, value);
1054 }
1055
1056 #ifdef DEBUG
1057 static void printSetupFileList(struct SetupFileList *setup_file_list)
1058 {
1059   if (!setup_file_list)
1060     return;
1061
1062   printf("token: '%s'\n", setup_file_list->token);
1063   printf("value: '%s'\n", setup_file_list->value);
1064
1065   printSetupFileList(setup_file_list->next);
1066 }
1067 #endif
1068
1069 struct SetupFileList *loadSetupFileList(char *filename)
1070 {
1071   int line_len;
1072   char line[MAX_LINE_LEN];
1073   char *token, *value, *line_ptr;
1074   struct SetupFileList *setup_file_list = newSetupFileList("", "");
1075   struct SetupFileList *first_valid_list_entry;
1076
1077   FILE *file;
1078
1079   if (!(file = fopen(filename, MODE_READ)))
1080   {
1081     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1082     return NULL;
1083   }
1084
1085   while(!feof(file))
1086   {
1087     /* read next line of input file */
1088     if (!fgets(line, MAX_LINE_LEN, file))
1089       break;
1090
1091     /* cut trailing comment or whitespace from input line */
1092     for (line_ptr = line; *line_ptr; line_ptr++)
1093     {
1094       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1095       {
1096         *line_ptr = '\0';
1097         break;
1098       }
1099     }
1100
1101     /* cut trailing whitespaces from input line */
1102     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1103       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1104         *line_ptr = '\0';
1105
1106     /* ignore empty lines */
1107     if (*line == '\0')
1108       continue;
1109
1110     line_len = strlen(line);
1111
1112     /* cut leading whitespaces from token */
1113     for (token = line; *token; token++)
1114       if (*token != ' ' && *token != '\t')
1115         break;
1116
1117     /* find end of token */
1118     for (line_ptr = token; *line_ptr; line_ptr++)
1119     {
1120       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1121       {
1122         *line_ptr = '\0';
1123         break;
1124       }
1125     }
1126
1127     if (line_ptr < line + line_len)
1128       value = line_ptr + 1;
1129     else
1130       value = "\0";
1131
1132     /* cut leading whitespaces from value */
1133     for (; *value; value++)
1134       if (*value != ' ' && *value != '\t')
1135         break;
1136
1137     if (*token && *value)
1138       setTokenValue(setup_file_list, token, value);
1139   }
1140
1141   fclose(file);
1142
1143   first_valid_list_entry = setup_file_list->next;
1144
1145   /* free empty list header */
1146   setup_file_list->next = NULL;
1147   freeSetupFileList(setup_file_list);
1148
1149   if (first_valid_list_entry == NULL)
1150     Error(ERR_WARN, "configuration file '%s' is empty", filename);
1151
1152   return first_valid_list_entry;
1153 }
1154
1155 void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
1156                                   char *identifier)
1157 {
1158   if (!setup_file_list)
1159     return;
1160
1161   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
1162   {
1163     if (!checkCookieString(setup_file_list->value, identifier))
1164     {
1165       Error(ERR_WARN, "configuration file has wrong file identifier");
1166       return;
1167     }
1168     else
1169       return;
1170   }
1171
1172   if (setup_file_list->next)
1173     checkSetupFileListIdentifier(setup_file_list->next, identifier);
1174   else
1175   {
1176     Error(ERR_WARN, "configuration file has no file identifier");
1177     return;
1178   }
1179 }
1180
1181
1182 /* ========================================================================= */
1183 /* setup file stuff                                                          */
1184 /* ========================================================================= */
1185
1186 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1187 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1188 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1189
1190 /* level directory info */
1191 #define LEVELINFO_TOKEN_NAME            0
1192 #define LEVELINFO_TOKEN_NAME_SHORT      1
1193 #define LEVELINFO_TOKEN_NAME_SORTING    2
1194 #define LEVELINFO_TOKEN_AUTHOR          3
1195 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1196 #define LEVELINFO_TOKEN_LEVELS          5
1197 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
1198 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
1199 #define LEVELINFO_TOKEN_LEVEL_GROUP     8
1200 #define LEVELINFO_TOKEN_READONLY        9
1201
1202 #define NUM_LEVELINFO_TOKENS            10
1203
1204 static LevelDirTree ldi;
1205
1206 static struct TokenInfo levelinfo_tokens[] =
1207 {
1208   /* level directory info */
1209   { TYPE_STRING,  &ldi.name,            "name"          },
1210   { TYPE_STRING,  &ldi.name_short,      "name_short"    },
1211   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1212   { TYPE_STRING,  &ldi.author,          "author"        },
1213   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1214   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1215   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1216   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1217   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1218   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      }
1219 };
1220
1221 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1222 {
1223   ldi->type = type;
1224
1225   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1226                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1227                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1228                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1229                    NULL);
1230
1231   ldi->node_parent = NULL;
1232   ldi->node_group = NULL;
1233   ldi->next = NULL;
1234
1235   ldi->cl_first = -1;
1236   ldi->cl_cursor = -1;
1237
1238   ldi->filename = NULL;
1239   ldi->fullpath = NULL;
1240   ldi->basepath = NULL;
1241   ldi->name = getStringCopy(ANONYMOUS_NAME);
1242   ldi->name_short = NULL;
1243   ldi->name_sorting = NULL;
1244   ldi->author = getStringCopy(ANONYMOUS_NAME);
1245
1246   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1247   ldi->parent_link = FALSE;
1248   ldi->user_defined = FALSE;
1249   ldi->color = 0;
1250   ldi->class_desc = NULL;
1251
1252   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1253   {
1254     ldi->imported_from = NULL;
1255     ldi->levels = 0;
1256     ldi->first_level = 0;
1257     ldi->last_level = 0;
1258     ldi->level_group = FALSE;
1259     ldi->handicap_level = 0;
1260     ldi->readonly = TRUE;
1261   }
1262 }
1263
1264 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1265 {
1266   if (parent == NULL)
1267   {
1268     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1269
1270     setTreeInfoToDefaults(ldi, TREE_TYPE_GENERIC);
1271     return;
1272   }
1273
1274   /* first copy all values from the parent structure ... */
1275   *ldi = *parent;
1276
1277   /* ... then set all fields to default that cannot be inherited from parent.
1278      This is especially important for all those fields that can be set from
1279      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1280      calls 'free()' for all already set token values which requires that no
1281      other structure's pointer may point to them!
1282   */
1283
1284   ldi->filename = NULL;
1285   ldi->fullpath = NULL;
1286   ldi->basepath = NULL;
1287   ldi->name = getStringCopy(ANONYMOUS_NAME);
1288   ldi->name_short = NULL;
1289   ldi->name_sorting = NULL;
1290   ldi->author = getStringCopy(parent->author);
1291   ldi->imported_from = getStringCopy(parent->imported_from);
1292
1293   ldi->level_group = FALSE;
1294   ldi->parent_link = FALSE;
1295
1296   ldi->node_top = parent->node_top;
1297   ldi->node_parent = parent;
1298   ldi->node_group = NULL;
1299   ldi->next = NULL;
1300 }
1301
1302 void setSetupInfo(struct TokenInfo *token_info,
1303                   int token_nr, char *token_value)
1304 {
1305   int token_type = token_info[token_nr].type;
1306   void *setup_value = token_info[token_nr].value;
1307
1308   if (token_value == NULL)
1309     return;
1310
1311   /* set setup field to corresponding token value */
1312   switch (token_type)
1313   {
1314     case TYPE_BOOLEAN:
1315     case TYPE_SWITCH:
1316       *(boolean *)setup_value = get_string_boolean_value(token_value);
1317       break;
1318
1319     case TYPE_KEY:
1320       *(Key *)setup_value = getKeyFromKeyName(token_value);
1321       break;
1322
1323     case TYPE_KEY_X11:
1324       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1325       break;
1326
1327     case TYPE_INTEGER:
1328       *(int *)setup_value = get_string_integer_value(token_value);
1329       break;
1330
1331     case TYPE_STRING:
1332       if (*(char **)setup_value != NULL)
1333         free(*(char **)setup_value);
1334       *(char **)setup_value = getStringCopy(token_value);
1335       break;
1336
1337     default:
1338       break;
1339   }
1340 }
1341
1342 static int compareTreeInfoEntries(const void *object1, const void *object2)
1343 {
1344   const TreeInfo *entry1 = *((TreeInfo **)object1);
1345   const TreeInfo *entry2 = *((TreeInfo **)object2);
1346   int compare_result;
1347
1348   if (entry1->parent_link || entry2->parent_link)
1349     compare_result = (entry1->parent_link ? -1 : +1);
1350   else if (entry1->sort_priority == entry2->sort_priority)
1351   {
1352     char *name1 = getStringToLower(entry1->name_sorting);
1353     char *name2 = getStringToLower(entry2->name_sorting);
1354
1355     compare_result = strcmp(name1, name2);
1356
1357     free(name1);
1358     free(name2);
1359   }
1360   else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1361     compare_result = entry1->sort_priority - entry2->sort_priority;
1362   else
1363     compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1364
1365   return compare_result;
1366 }
1367
1368 static void createParentTreeInfoNode(TreeInfo *node_parent)
1369 {
1370   TreeInfo *ti_new;
1371
1372   if (node_parent == NULL)
1373     return;
1374
1375   ti_new = newTreeInfo();
1376   setTreeInfoToDefaults(ti_new, node_parent->type);
1377
1378   ti_new->node_parent = node_parent;
1379   ti_new->parent_link = TRUE;
1380
1381   ti_new->name = ".. (parent directory)";
1382   ti_new->name_short = getStringCopy(ti_new->name);
1383   ti_new->name_sorting = getStringCopy(ti_new->name);
1384
1385   ti_new->filename = "..";
1386   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1387
1388   ti_new->sort_priority = node_parent->sort_priority;
1389   ti_new->class_desc = getLevelClassDescription(ti_new);
1390
1391   pushTreeInfo(&node_parent->node_group, ti_new);
1392 }
1393
1394 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1395 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1396
1397 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1398                                           TreeInfo *node_parent,
1399                                           char *level_directory,
1400                                           char *directory_name)
1401 {
1402   char *directory_path = getPath2(level_directory, directory_name);
1403   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1404   struct SetupFileList *setup_file_list = loadSetupFileList(filename);
1405   LevelDirTree *leveldir_new = NULL;
1406   int i;
1407
1408   if (setup_file_list == NULL)
1409   {
1410     Error(ERR_WARN, "ignoring level directory '%s'", level_directory);
1411
1412     free(directory_path);
1413     free(filename);
1414
1415     return FALSE;
1416   }
1417
1418   leveldir_new = newTreeInfo();
1419
1420   if (node_parent)
1421     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1422   else
1423     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1424
1425   checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1426
1427   /* set all structure fields according to the token/value pairs */
1428   ldi = *leveldir_new;
1429   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1430     setSetupInfo(levelinfo_tokens, i,
1431                  getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1432   *leveldir_new = ldi;
1433
1434   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1435
1436   if (leveldir_new->name_short == NULL)
1437     leveldir_new->name_short = getStringCopy(leveldir_new->name);
1438
1439   if (leveldir_new->name_sorting == NULL)
1440     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1441
1442   leveldir_new->filename = getStringCopy(directory_name);
1443
1444   if (node_parent == NULL)              /* top level group */
1445   {
1446     leveldir_new->basepath = level_directory;
1447     leveldir_new->fullpath = leveldir_new->filename;
1448   }
1449   else                                  /* sub level group */
1450   {
1451     leveldir_new->basepath = node_parent->basepath;
1452     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1453   }
1454
1455   if (leveldir_new->levels < 1)
1456     leveldir_new->levels = 1;
1457
1458   leveldir_new->last_level =
1459     leveldir_new->first_level + leveldir_new->levels - 1;
1460
1461   leveldir_new->user_defined =
1462     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1463
1464   leveldir_new->color = LEVELCOLOR(leveldir_new);
1465   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1466
1467   leveldir_new->handicap_level =        /* set handicap to default value */
1468     (leveldir_new->user_defined ?
1469      leveldir_new->last_level :
1470      leveldir_new->first_level);
1471
1472   pushTreeInfo(node_first, leveldir_new);
1473
1474   freeSetupFileList(setup_file_list);
1475
1476   if (leveldir_new->level_group)
1477   {
1478     /* create node to link back to current level directory */
1479     createParentTreeInfoNode(leveldir_new);
1480
1481     /* step into sub-directory and look for more level series */
1482     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1483                               leveldir_new, directory_path);
1484   }
1485
1486   free(directory_path);
1487   free(filename);
1488
1489   return TRUE;
1490 }
1491
1492 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1493                                       TreeInfo *node_parent,
1494                                       char *level_directory)
1495 {
1496   DIR *dir;
1497   struct dirent *dir_entry;
1498   boolean valid_entry_found = FALSE;
1499
1500   if ((dir = opendir(level_directory)) == NULL)
1501   {
1502     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1503     return;
1504   }
1505
1506   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1507   {
1508     struct stat file_status;
1509     char *directory_name = dir_entry->d_name;
1510     char *directory_path = getPath2(level_directory, directory_name);
1511
1512     /* skip entries for current and parent directory */
1513     if (strcmp(directory_name, ".")  == 0 ||
1514         strcmp(directory_name, "..") == 0)
1515     {
1516       free(directory_path);
1517       continue;
1518     }
1519
1520     /* find out if directory entry is itself a directory */
1521     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1522         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1523     {
1524       free(directory_path);
1525       continue;
1526     }
1527
1528     free(directory_path);
1529
1530     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1531                                                     level_directory,
1532                                                     directory_name);
1533   }
1534
1535   closedir(dir);
1536
1537   if (!valid_entry_found)
1538   {
1539     /* check if this directory directly contains a file "levelinfo.conf" */
1540     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1541                                                     level_directory, ".");
1542   }
1543
1544   if (!valid_entry_found)
1545     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1546           level_directory);
1547 }
1548
1549 void LoadLevelInfo()
1550 {
1551   InitUserLevelDirectory(getLoginName());
1552
1553   DrawInitText("Loading level series:", 120, FC_GREEN);
1554
1555   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1556   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1557
1558   /* before sorting, the first entries will be from the user directory */
1559   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1560
1561   if (leveldir_first == NULL)
1562     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1563
1564   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1565
1566 #if 0
1567   dumpTreeInfo(leveldir_first, 0);
1568 #endif
1569 }
1570
1571 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1572                                               TreeInfo *node_parent,
1573                                               char *base_directory,
1574                                               char *directory_name, int type)
1575 {
1576   char *directory_path = getPath2(base_directory, directory_name);
1577   char *filename =
1578     getPath2(directory_path,
1579              (type == TREE_TYPE_GRAPHICS_DIR ? GRAPHICSINFO_FILENAME :
1580               type == TREE_TYPE_SOUNDS_DIR ? SOUNDSINFO_FILENAME :
1581               type == TREE_TYPE_MUSIC_DIR ? MUSICINFO_FILENAME : ""));
1582   struct SetupFileList *setup_file_list = NULL;
1583   TreeInfo *artwork_new = NULL;
1584   char *check_dir = NULL;
1585   int i;
1586
1587   if (access(filename, F_OK) == 0)              /* file exists */
1588     loadSetupFileList(filename);
1589
1590   if (setup_file_list == NULL)  /* no config file -- look for artwork files */
1591   {
1592     DIR *dir;
1593     struct dirent *dir_entry;
1594     boolean valid_file_found = FALSE;
1595
1596     if ((dir = opendir(directory_path)) != NULL)
1597     {
1598       while ((dir_entry = readdir(dir)) != NULL)
1599       {
1600         char *entry_name = dir_entry->d_name;
1601
1602         if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(entry_name)) ||
1603             (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(entry_name)) ||
1604             (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(entry_name)))
1605         {
1606           valid_file_found = TRUE;
1607           break;
1608         }
1609       }
1610
1611       closedir(dir);
1612     }
1613
1614     if (!valid_file_found)
1615     {
1616       if (options.debug)
1617         Error(ERR_WARN, "ignoring artwork directory '%s'", base_directory);
1618
1619       free(directory_path);
1620       free(filename);
1621
1622       return FALSE;
1623     }
1624   }
1625
1626   artwork_new = newTreeInfo();
1627
1628   if (node_parent)
1629     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1630   else
1631     setTreeInfoToDefaults(artwork_new, type);
1632
1633   artwork_new->filename = getStringCopy(directory_name);
1634
1635   if (setup_file_list)  /* (before defining ".color" and ".class_desc") */
1636   {
1637 #if 0
1638     checkSetupFileListIdentifier(setup_file_list, getCookie("..."));
1639 #endif
1640
1641     /* set all structure fields according to the token/value pairs */
1642     ldi = *artwork_new;
1643     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1644       setSetupInfo(levelinfo_tokens, i,
1645                    getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1646     *artwork_new = ldi;
1647
1648     DrawInitText(artwork_new->name, 150, FC_YELLOW);
1649
1650     if (artwork_new->name_short == NULL)
1651       artwork_new->name_short = getStringCopy(artwork_new->name);
1652
1653     if (artwork_new->name_sorting == NULL)
1654       artwork_new->name_sorting = getStringCopy(artwork_new->name);
1655   }
1656
1657   if (node_parent == NULL)              /* top level group */
1658   {
1659     artwork_new->basepath = getStringCopy(base_directory);
1660     artwork_new->fullpath = getStringCopy(artwork_new->filename);
1661   }
1662   else                                  /* sub level group */
1663   {
1664     artwork_new->basepath = getStringCopy(node_parent->basepath);
1665     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1666   }
1667
1668   check_dir = (type == TREE_TYPE_GRAPHICS_DIR ? options.graphics_directory :
1669                type == TREE_TYPE_SOUNDS_DIR ? options.sounds_directory :
1670                type == TREE_TYPE_MUSIC_DIR ? options.music_directory : "");
1671   artwork_new->user_defined =
1672     (artwork_new->basepath == check_dir ? FALSE : TRUE);
1673
1674   /* (may use ".sort_priority" from "setup_file_list" above) */
1675   artwork_new->color = LEVELCOLOR(artwork_new);
1676   artwork_new->class_desc = getLevelClassDescription(artwork_new);
1677
1678   if (setup_file_list == NULL)  /* (after determining ".user_defined") */
1679   {
1680     if (artwork_new->name != NULL)
1681       free(artwork_new->name);
1682
1683     if (strcmp(artwork_new->filename, ".") == 0)
1684     {
1685       if (artwork_new->user_defined)
1686         artwork_new->name = getStringCopy("private");
1687       else
1688         artwork_new->name = getStringCopy("default");
1689     }
1690     else
1691       artwork_new->name = getStringCopy(artwork_new->filename);
1692
1693     artwork_new->name_short = getStringCopy(artwork_new->name);
1694     artwork_new->name_sorting = getStringCopy(artwork_new->name);
1695   }
1696
1697   pushTreeInfo(node_first, artwork_new);
1698
1699   freeSetupFileList(setup_file_list);
1700
1701   free(directory_path);
1702   free(filename);
1703
1704   return TRUE;
1705 }
1706
1707 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
1708                                           TreeInfo *node_parent,
1709                                           char *base_directory, int type)
1710 {
1711   DIR *dir;
1712   struct dirent *dir_entry;
1713   boolean valid_entry_found = FALSE;
1714
1715   if ((dir = opendir(base_directory)) == NULL)
1716   {
1717     if ((type == TREE_TYPE_GRAPHICS_DIR &&
1718          base_directory == options.graphics_directory) ||
1719         (type == TREE_TYPE_SOUNDS_DIR &&
1720          base_directory == options.sounds_directory) ||
1721         (type == TREE_TYPE_MUSIC_DIR &&
1722          base_directory == options.music_directory))
1723       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
1724     return;
1725   }
1726
1727   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1728   {
1729     struct stat file_status;
1730     char *directory_name = dir_entry->d_name;
1731     char *directory_path = getPath2(base_directory, directory_name);
1732
1733     /* skip entries for current and parent directory */
1734     if (strcmp(directory_name, ".")  == 0 ||
1735         strcmp(directory_name, "..") == 0)
1736     {
1737       free(directory_path);
1738       continue;
1739     }
1740
1741     /* find out if directory entry is itself a directory */
1742     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1743         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1744     {
1745       free(directory_path);
1746       continue;
1747     }
1748
1749     free(directory_path);
1750
1751     /* check if this directory contains artwork with or without config file */
1752     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1753                                                         base_directory,
1754                                                         directory_name, type);
1755   }
1756
1757   closedir(dir);
1758
1759   /* check if this directory directly contains artwork itself */
1760   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1761                                                       base_directory, ".",
1762                                                       type);
1763   if (!valid_entry_found)
1764     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
1765           base_directory);
1766 }
1767
1768 void LoadArtworkInfo()
1769 {
1770   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
1771
1772   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1773                                 options.graphics_directory,
1774                                 TREE_TYPE_GRAPHICS_DIR);
1775   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1776                                 getUserGraphicsDir(),
1777                                 TREE_TYPE_GRAPHICS_DIR);
1778
1779   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1780                                 options.sounds_directory,
1781                                 TREE_TYPE_SOUNDS_DIR);
1782   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1783                                 getUserSoundsDir(),
1784                                 TREE_TYPE_SOUNDS_DIR);
1785
1786   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1787                                 options.music_directory,
1788                                 TREE_TYPE_MUSIC_DIR);
1789   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1790                                 getUserMusicDir(),
1791                                 TREE_TYPE_MUSIC_DIR);
1792
1793   /* before sorting, the first entries will be from the user directory */
1794   artwork.gfx_current =
1795     getTreeInfoFromFilename(artwork.gfx_first, setup.graphics_set);
1796   if (artwork.gfx_current == NULL)
1797     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
1798
1799   artwork.snd_current =
1800     getTreeInfoFromFilename(artwork.snd_first, setup.sounds_set);
1801   if (artwork.snd_current == NULL)
1802   artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
1803
1804   artwork.mus_current =
1805     getTreeInfoFromFilename(artwork.mus_first, setup.music_set);
1806   if (artwork.mus_current == NULL)
1807   artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
1808
1809   artwork.graphics_set_current = artwork.gfx_current->name;
1810   artwork.sounds_set_current = artwork.snd_current->name;
1811   artwork.music_set_current = artwork.mus_current->name;
1812
1813 #if 0
1814   printf("graphics set == %s\n\n", artwork.graphics_set_current);
1815   printf("sounds set == %s\n\n", artwork.sounds_set_current);
1816   printf("music set == %s\n\n", artwork.music_set_current);
1817 #endif
1818
1819   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
1820   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
1821   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
1822
1823 #if 0
1824   dumpTreeInfo(artwork.gfx_first, 0);
1825   dumpTreeInfo(artwork.snd_first, 0);
1826   dumpTreeInfo(artwork.mus_first, 0);
1827 #endif
1828 }
1829
1830 static void SaveUserLevelInfo()
1831 {
1832   char *filename;
1833   FILE *file;
1834   int i;
1835
1836   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1837
1838   if (!(file = fopen(filename, MODE_WRITE)))
1839   {
1840     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1841     free(filename);
1842     return;
1843   }
1844
1845   /* always start with reliable default values */
1846   setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
1847
1848   ldi.name = getLoginName();
1849   ldi.author = getRealName();
1850   ldi.levels = 100;
1851   ldi.first_level = 1;
1852   ldi.sort_priority = LEVELCLASS_USER_START;
1853   ldi.readonly = FALSE;
1854
1855   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1856                                                  getCookie("LEVELINFO")));
1857
1858   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1859     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1860         i != LEVELINFO_TOKEN_NAME_SORTING &&
1861         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1862       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1863
1864   fclose(file);
1865   free(filename);
1866
1867   SetFilePermissions(filename, PERMS_PRIVATE);
1868 }
1869
1870 char *getSetupValue(int type, void *value)
1871 {
1872   static char value_string[MAX_LINE_LEN];
1873
1874   switch (type)
1875   {
1876     case TYPE_BOOLEAN:
1877       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
1878       break;
1879
1880     case TYPE_SWITCH:
1881       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
1882       break;
1883
1884     case TYPE_YES_NO:
1885       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
1886       break;
1887
1888     case TYPE_KEY:
1889       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
1890       break;
1891
1892     case TYPE_KEY_X11:
1893       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
1894       break;
1895
1896     case TYPE_INTEGER:
1897       sprintf(value_string, "%d", *(int *)value);
1898       break;
1899
1900     case TYPE_STRING:
1901       strcpy(value_string, *(char **)value);
1902       break;
1903
1904     default:
1905       value_string[0] = '\0';
1906       break;
1907   }
1908
1909   return value_string;
1910 }
1911
1912 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1913 {
1914   int i;
1915   char *line;
1916   static char token_string[MAX_LINE_LEN];
1917   int token_type = token_info[token_nr].type;
1918   void *setup_value = token_info[token_nr].value;
1919   char *token_text = token_info[token_nr].text;
1920   char *value_string = getSetupValue(token_type, setup_value);
1921
1922   /* build complete token string */
1923   sprintf(token_string, "%s%s", prefix, token_text);
1924
1925   /* build setup entry line */
1926   line = getFormattedSetupEntry(token_string, value_string);
1927
1928   if (token_type == TYPE_KEY_X11)
1929   {
1930     Key key = *(Key *)setup_value;
1931     char *keyname = getKeyNameFromKey(key);
1932
1933     /* add comment, if useful */
1934     if (strcmp(keyname, "(undefined)") != 0 &&
1935         strcmp(keyname, "(unknown)") != 0)
1936     {
1937       /* add at least one whitespace */
1938       strcat(line, " ");
1939       for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
1940         strcat(line, " ");
1941
1942       strcat(line, "# ");
1943       strcat(line, keyname);
1944     }
1945   }
1946
1947   return line;
1948 }
1949
1950 void LoadLevelSetup_LastSeries()
1951 {
1952   char *filename;
1953   struct SetupFileList *level_setup_list = NULL;
1954
1955   /* always start with reliable default values */
1956   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1957
1958   /* ----------------------------------------------------------------------- */
1959   /* ~/.<program>/levelsetup.conf                                            */
1960   /* ----------------------------------------------------------------------- */
1961
1962   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1963
1964   if ((level_setup_list = loadSetupFileList(filename)))
1965   {
1966     char *last_level_series =
1967       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1968
1969     leveldir_current = getTreeInfoFromFilename(leveldir_first,
1970                                                last_level_series);
1971     if (leveldir_current == NULL)
1972       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1973
1974     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1975
1976     freeSetupFileList(level_setup_list);
1977   }
1978   else
1979     Error(ERR_WARN, "using default setup values");
1980
1981   free(filename);
1982 }
1983
1984 void SaveLevelSetup_LastSeries()
1985 {
1986   char *filename;
1987   char *level_subdir = leveldir_current->filename;
1988   FILE *file;
1989
1990   /* ----------------------------------------------------------------------- */
1991   /* ~/.<program>/levelsetup.conf                                            */
1992   /* ----------------------------------------------------------------------- */
1993
1994   InitUserDataDirectory();
1995
1996   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1997
1998   if (!(file = fopen(filename, MODE_WRITE)))
1999   {
2000     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2001     free(filename);
2002     return;
2003   }
2004
2005   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2006                                                  getCookie("LEVELSETUP")));
2007   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2008                                                level_subdir));
2009
2010   fclose(file);
2011   free(filename);
2012
2013   SetFilePermissions(filename, PERMS_PRIVATE);
2014 }
2015
2016 static void checkSeriesInfo()
2017 {
2018   static char *level_directory = NULL;
2019   DIR *dir;
2020   struct dirent *dir_entry;
2021
2022   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2023
2024   level_directory = getPath2((leveldir_current->user_defined ?
2025                               getUserLevelDir(NULL) :
2026                               options.level_directory),
2027                              leveldir_current->fullpath);
2028
2029   if ((dir = opendir(level_directory)) == NULL)
2030   {
2031     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2032     return;
2033   }
2034
2035   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2036   {
2037     if (strlen(dir_entry->d_name) > 4 &&
2038         dir_entry->d_name[3] == '.' &&
2039         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2040     {
2041       char levelnum_str[4];
2042       int levelnum_value;
2043
2044       strncpy(levelnum_str, dir_entry->d_name, 3);
2045       levelnum_str[3] = '\0';
2046
2047       levelnum_value = atoi(levelnum_str);
2048
2049       if (levelnum_value < leveldir_current->first_level)
2050       {
2051         Error(ERR_WARN, "additional level %d found", levelnum_value);
2052         leveldir_current->first_level = levelnum_value;
2053       }
2054       else if (levelnum_value > leveldir_current->last_level)
2055       {
2056         Error(ERR_WARN, "additional level %d found", levelnum_value);
2057         leveldir_current->last_level = levelnum_value;
2058       }
2059     }
2060   }
2061
2062   closedir(dir);
2063 }
2064
2065 void LoadLevelSetup_SeriesInfo()
2066 {
2067   char *filename;
2068   struct SetupFileList *level_setup_list = NULL;
2069   char *level_subdir = leveldir_current->filename;
2070
2071   /* always start with reliable default values */
2072   level_nr = leveldir_current->first_level;
2073
2074   checkSeriesInfo(leveldir_current);
2075
2076   /* ----------------------------------------------------------------------- */
2077   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2078   /* ----------------------------------------------------------------------- */
2079
2080   level_subdir = leveldir_current->filename;
2081
2082   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2083
2084   if ((level_setup_list = loadSetupFileList(filename)))
2085   {
2086     char *token_value;
2087
2088     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
2089
2090     if (token_value)
2091     {
2092       level_nr = atoi(token_value);
2093
2094       if (level_nr < leveldir_current->first_level)
2095         level_nr = leveldir_current->first_level;
2096       if (level_nr > leveldir_current->last_level)
2097         level_nr = leveldir_current->last_level;
2098     }
2099
2100     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
2101
2102     if (token_value)
2103     {
2104       int level_nr = atoi(token_value);
2105
2106       if (level_nr < leveldir_current->first_level)
2107         level_nr = leveldir_current->first_level;
2108       if (level_nr > leveldir_current->last_level + 1)
2109         level_nr = leveldir_current->last_level;
2110
2111       if (leveldir_current->user_defined)
2112         level_nr = leveldir_current->last_level;
2113
2114       leveldir_current->handicap_level = level_nr;
2115     }
2116
2117     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
2118
2119     freeSetupFileList(level_setup_list);
2120   }
2121   else
2122     Error(ERR_WARN, "using default setup values");
2123
2124   free(filename);
2125 }
2126
2127 void SaveLevelSetup_SeriesInfo()
2128 {
2129   char *filename;
2130   char *level_subdir = leveldir_current->filename;
2131   char *level_nr_str = int2str(level_nr, 0);
2132   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2133   FILE *file;
2134
2135   /* ----------------------------------------------------------------------- */
2136   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2137   /* ----------------------------------------------------------------------- */
2138
2139   InitLevelSetupDirectory(level_subdir);
2140
2141   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2142
2143   if (!(file = fopen(filename, MODE_WRITE)))
2144   {
2145     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2146     free(filename);
2147     return;
2148   }
2149
2150   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2151                                                  getCookie("LEVELSETUP")));
2152   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2153                                                level_nr_str));
2154   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2155                                                handicap_level_str));
2156
2157   fclose(file);
2158   free(filename);
2159
2160   SetFilePermissions(filename, PERMS_PRIVATE);
2161 }