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