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