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