rocksndiamonds-3.0.8
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <dirent.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "setup.h"
21 #include "joystick.h"
22 #include "text.h"
23 #include "misc.h"
24 #include "hash.h"
25
26
27 #define NUM_LEVELCLASS_DESC     8
28
29 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
30 {
31   "Tutorial Levels",
32   "Classic Originals",
33   "Contributions",
34   "Private Levels",
35   "Boulderdash",
36   "Emerald Mine",
37   "Supaplex",
38   "DX Boulderdash"
39 };
40
41
42 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
43                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
44                          IS_LEVELCLASS_BD(n) ?                  FC_GREEN :   \
45                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
46                          IS_LEVELCLASS_SP(n) ?                  FC_GREEN :   \
47                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
48                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
49                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
50                          FC_BLUE)
51
52 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
53                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
54                          IS_LEVELCLASS_BD(n) ?                  2 :     \
55                          IS_LEVELCLASS_EM(n) ?                  3 :     \
56                          IS_LEVELCLASS_SP(n) ?                  4 :     \
57                          IS_LEVELCLASS_DX(n) ?                  5 :     \
58                          IS_LEVELCLASS_CONTRIB(n) ?             6 :     \
59                          IS_LEVELCLASS_PRIVATE(n) ?             7 :     \
60                          9)
61
62 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
63                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_YELLOW :  \
64                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
65                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_GREEN :   \
66                          FC_BLUE)
67
68 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
69                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
70                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
71                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
72                            9)
73
74 #define TOKEN_VALUE_POSITION            40
75 #define TOKEN_COMMENT_POSITION          60
76
77 #define MAX_COOKIE_LEN                  256
78
79
80 /* ------------------------------------------------------------------------- */
81 /* file functions                                                            */
82 /* ------------------------------------------------------------------------- */
83
84 static char *getLevelClassDescription(TreeInfo *ldi)
85 {
86   int position = ldi->sort_priority / 100;
87
88   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
89     return levelclass_desc[position];
90   else
91     return "Unknown Level Class";
92 }
93
94 static char *getUserLevelDir(char *level_subdir)
95 {
96   static char *userlevel_dir = NULL;
97   char *data_dir = getUserDataDir();
98   char *userlevel_subdir = LEVELS_DIRECTORY;
99
100   checked_free(userlevel_dir);
101
102   if (level_subdir != NULL)
103     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
104   else
105     userlevel_dir = getPath2(data_dir, userlevel_subdir);
106
107   return userlevel_dir;
108 }
109
110 static char *getScoreDir(char *level_subdir)
111 {
112   static char *score_dir = NULL;
113   char *data_dir = getCommonDataDir();
114   char *score_subdir = SCORES_DIRECTORY;
115
116   checked_free(score_dir);
117
118   if (level_subdir != NULL)
119     score_dir = getPath3(data_dir, score_subdir, level_subdir);
120   else
121     score_dir = getPath2(data_dir, score_subdir);
122
123   return score_dir;
124 }
125
126 static char *getLevelSetupDir(char *level_subdir)
127 {
128   static char *levelsetup_dir = NULL;
129   char *data_dir = getUserDataDir();
130   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
131
132   checked_free(levelsetup_dir);
133
134   if (level_subdir != NULL)
135     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
136   else
137     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
138
139   return levelsetup_dir;
140 }
141
142 static char *getLevelDirFromTreeInfo(TreeInfo *node)
143 {
144   static char *level_dir = NULL;
145
146   if (node == NULL)
147     return options.level_directory;
148
149   checked_free(level_dir);
150
151   level_dir = getPath2((node->user_defined ? getUserLevelDir(NULL) :
152                         options.level_directory), node->fullpath);
153
154   return level_dir;
155 }
156
157 static char *getCurrentLevelDir()
158 {
159   return getLevelDirFromTreeInfo(leveldir_current);
160 }
161
162 static char *getTapeDir(char *level_subdir)
163 {
164   static char *tape_dir = NULL;
165   char *data_dir = getUserDataDir();
166   char *tape_subdir = TAPES_DIRECTORY;
167
168   checked_free(tape_dir);
169
170   if (level_subdir != NULL)
171     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
172   else
173     tape_dir = getPath2(data_dir, tape_subdir);
174
175   return tape_dir;
176 }
177
178 static char *getSolutionTapeDir()
179 {
180   static char *tape_dir = NULL;
181   char *data_dir = getCurrentLevelDir();
182   char *tape_subdir = TAPES_DIRECTORY;
183
184   checked_free(tape_dir);
185
186   tape_dir = getPath2(data_dir, tape_subdir);
187
188   return tape_dir;
189 }
190
191 static char *getDefaultGraphicsDir(char *graphics_subdir)
192 {
193   static char *graphics_dir = NULL;
194
195   if (graphics_subdir == NULL)
196     return options.graphics_directory;
197
198   checked_free(graphics_dir);
199
200   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
201
202   return graphics_dir;
203 }
204
205 static char *getDefaultSoundsDir(char *sounds_subdir)
206 {
207   static char *sounds_dir = NULL;
208
209   if (sounds_subdir == NULL)
210     return options.sounds_directory;
211
212   checked_free(sounds_dir);
213
214   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
215
216   return sounds_dir;
217 }
218
219 static char *getDefaultMusicDir(char *music_subdir)
220 {
221   static char *music_dir = NULL;
222
223   if (music_subdir == NULL)
224     return options.music_directory;
225
226   checked_free(music_dir);
227
228   music_dir = getPath2(options.music_directory, music_subdir);
229
230   return music_dir;
231 }
232
233 static char *getDefaultArtworkSet(int type)
234 {
235   return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
236           type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
237           type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
238 }
239
240 static char *getDefaultArtworkDir(int type)
241 {
242   return (type == TREE_TYPE_GRAPHICS_DIR ?
243           getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
244           type == TREE_TYPE_SOUNDS_DIR ?
245           getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
246           type == TREE_TYPE_MUSIC_DIR ?
247           getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
248 }
249
250 static char *getUserGraphicsDir()
251 {
252   static char *usergraphics_dir = NULL;
253
254   if (usergraphics_dir == NULL)
255     usergraphics_dir = getPath2(getUserDataDir(), GRAPHICS_DIRECTORY);
256
257   return usergraphics_dir;
258 }
259
260 static char *getUserSoundsDir()
261 {
262   static char *usersounds_dir = NULL;
263
264   if (usersounds_dir == NULL)
265     usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
266
267   return usersounds_dir;
268 }
269
270 static char *getUserMusicDir()
271 {
272   static char *usermusic_dir = NULL;
273
274   if (usermusic_dir == NULL)
275     usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
276
277   return usermusic_dir;
278 }
279
280 static char *getSetupArtworkDir(TreeInfo *ti)
281 {
282   static char *artwork_dir = NULL;
283
284   checked_free(artwork_dir);
285
286   artwork_dir = getPath2(ti->basepath, ti->fullpath);
287
288   return artwork_dir;
289 }
290
291 char *setLevelArtworkDir(TreeInfo *ti)
292 {
293   char **artwork_path_ptr, **artwork_set_ptr;
294   TreeInfo *level_artwork;
295
296   if (ti == NULL || leveldir_current == NULL)
297     return NULL;
298
299   artwork_path_ptr = &(LEVELDIR_ARTWORK_PATH(leveldir_current, ti->type));
300   artwork_set_ptr  = &(LEVELDIR_ARTWORK_SET( leveldir_current, ti->type));
301
302   checked_free(*artwork_path_ptr);
303
304   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
305     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
306   else
307   {
308     /* No (or non-existing) artwork configured in "levelinfo.conf". This would
309        normally result in using the artwork configured in the setup menu. But
310        if an artwork subdirectory exists (which might contain custom artwork
311        or an artwork configuration file), this level artwork must be treated
312        as relative to the default "classic" artwork, not to the artwork that
313        is currently configured in the setup menu. */
314
315     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
316
317     checked_free(*artwork_set_ptr);
318
319     if (fileExists(dir))
320     {
321       *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
322       *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
323     }
324     else
325     {
326       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
327       *artwork_set_ptr = NULL;
328     }
329
330     free(dir);
331   }
332
333   return *artwork_set_ptr;
334 }
335
336 inline static char *getLevelArtworkSet(int type)
337 {
338   if (leveldir_current == NULL)
339     return NULL;
340
341   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
342 }
343
344 inline static char *getLevelArtworkDir(int type)
345 {
346   if (leveldir_current == NULL)
347     return UNDEFINED_FILENAME;
348
349   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
350 }
351
352 char *getLevelFilename(int nr)
353 {
354   static char *filename = NULL;
355   char basename[MAX_FILENAME_LEN];
356
357   checked_free(filename);
358
359   if (nr < 0)
360     sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
361   else
362     sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
363
364   filename = getPath2(getCurrentLevelDir(), basename);
365
366   return filename;
367 }
368
369 char *getTapeFilename(int nr)
370 {
371   static char *filename = NULL;
372   char basename[MAX_FILENAME_LEN];
373
374   checked_free(filename);
375
376   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
377   filename = getPath2(getTapeDir(leveldir_current->filename), basename);
378
379   return filename;
380 }
381
382 char *getSolutionTapeFilename(int nr)
383 {
384   static char *filename = NULL;
385   char basename[MAX_FILENAME_LEN];
386
387   checked_free(filename);
388
389   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
390   filename = getPath2(getSolutionTapeDir(), basename);
391
392   return filename;
393 }
394
395 char *getScoreFilename(int nr)
396 {
397   static char *filename = NULL;
398   char basename[MAX_FILENAME_LEN];
399
400   checked_free(filename);
401
402   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
403   filename = getPath2(getScoreDir(leveldir_current->filename), basename);
404
405   return filename;
406 }
407
408 char *getSetupFilename()
409 {
410   static char *filename = NULL;
411
412   checked_free(filename);
413
414   filename = getPath2(getSetupDir(), SETUP_FILENAME);
415
416   return filename;
417 }
418
419 char *getEditorSetupFilename()
420 {
421   static char *filename = NULL;
422
423   checked_free(filename);
424
425   filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
426
427   return filename;
428 }
429
430 char *getHelpAnimFilename()
431 {
432   static char *filename = NULL;
433
434   checked_free(filename);
435
436   filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
437
438   return filename;
439 }
440
441 char *getHelpTextFilename()
442 {
443   static char *filename = NULL;
444
445   checked_free(filename);
446
447   filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
448
449   return filename;
450 }
451
452 char *getLevelSetInfoFilename()
453 {
454   static char *filename = NULL;
455   char *basenames[] =
456   {
457     "README",
458     "README.txt",
459     "README.TXT",
460     "Readme",
461     "Readme.txt",
462     "readme",
463     "readme.txt",
464
465     NULL
466   };
467   int i;
468
469   for (i = 0; basenames[i] != NULL; i++)
470   {
471     checked_free(filename);
472
473     filename = getPath2(getCurrentLevelDir(), basenames[i]);
474     if (fileExists(filename))
475       return filename;
476   }
477
478   return NULL;
479 }
480
481 static char *getCorrectedArtworkBasename(char *basename)
482 {
483   char *basename_corrected = basename;
484
485 #if defined(PLATFORM_MSDOS)
486   if (program.filename_prefix != NULL)
487   {
488     int prefix_len = strlen(program.filename_prefix);
489
490     if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
491       basename_corrected = &basename[prefix_len];
492
493     /* if corrected filename is still longer than standard MS-DOS filename
494        size (8 characters + 1 dot + 3 characters file extension), shorten
495        filename by writing file extension after 8th basename character */
496     if (strlen(basename_corrected) > 8 + 1 + 3)
497     {
498       static char *msdos_filename = NULL;
499
500       checked_free(msdos_filename);
501
502       msdos_filename = getStringCopy(basename_corrected);
503       strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
504
505       basename_corrected = msdos_filename;
506     }
507   }
508 #endif
509
510   return basename_corrected;
511 }
512
513 char *getCustomImageFilename(char *basename)
514 {
515   static char *filename = NULL;
516   boolean skip_setup_artwork = FALSE;
517
518   checked_free(filename);
519
520   basename = getCorrectedArtworkBasename(basename);
521
522   if (!setup.override_level_graphics)
523   {
524     /* 1st try: look for special artwork in current level series directory */
525     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
526     if (fileExists(filename))
527       return filename;
528
529     free(filename);
530
531     /* check if there is special artwork configured in level series config */
532     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
533     {
534       /* 2nd try: look for special artwork configured in level series config */
535       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
536       if (fileExists(filename))
537         return filename;
538
539       free(filename);
540
541       /* take missing artwork configured in level set config from default */
542       skip_setup_artwork = TRUE;
543     }
544   }
545
546   if (!skip_setup_artwork)
547   {
548     /* 3rd try: look for special artwork in configured artwork directory */
549     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
550     if (fileExists(filename))
551       return filename;
552
553     free(filename);
554   }
555
556   /* 4th try: look for default artwork in new default artwork directory */
557   filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
558   if (fileExists(filename))
559     return filename;
560
561   free(filename);
562
563   /* 5th try: look for default artwork in old default artwork directory */
564   filename = getPath2(options.graphics_directory, basename);
565   if (fileExists(filename))
566     return filename;
567
568   return NULL;          /* cannot find specified artwork file anywhere */
569 }
570
571 char *getCustomSoundFilename(char *basename)
572 {
573   static char *filename = NULL;
574   boolean skip_setup_artwork = FALSE;
575
576   checked_free(filename);
577
578   basename = getCorrectedArtworkBasename(basename);
579
580   if (!setup.override_level_sounds)
581   {
582     /* 1st try: look for special artwork in current level series directory */
583     filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
584     if (fileExists(filename))
585       return filename;
586
587     free(filename);
588
589     /* check if there is special artwork configured in level series config */
590     if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
591     {
592       /* 2nd try: look for special artwork configured in level series config */
593       filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
594       if (fileExists(filename))
595         return filename;
596
597       free(filename);
598
599       /* take missing artwork configured in level set config from default */
600       skip_setup_artwork = TRUE;
601     }
602   }
603
604   if (!skip_setup_artwork)
605   {
606     /* 3rd try: look for special artwork in configured artwork directory */
607     filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
608     if (fileExists(filename))
609       return filename;
610
611     free(filename);
612   }
613
614   /* 4th try: look for default artwork in new default artwork directory */
615   filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
616   if (fileExists(filename))
617     return filename;
618
619   free(filename);
620
621   /* 5th try: look for default artwork in old default artwork directory */
622   filename = getPath2(options.sounds_directory, basename);
623   if (fileExists(filename))
624     return filename;
625
626   return NULL;          /* cannot find specified artwork file anywhere */
627 }
628
629 char *getCustomMusicFilename(char *basename)
630 {
631   static char *filename = NULL;
632   boolean skip_setup_artwork = FALSE;
633
634   checked_free(filename);
635
636   basename = getCorrectedArtworkBasename(basename);
637
638   if (!setup.override_level_music)
639   {
640     /* 1st try: look for special artwork in current level series directory */
641     filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
642     if (fileExists(filename))
643       return filename;
644
645     free(filename);
646
647     /* check if there is special artwork configured in level series config */
648     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
649     {
650       /* 2nd try: look for special artwork configured in level series config */
651       filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
652       if (fileExists(filename))
653         return filename;
654
655       free(filename);
656
657       /* take missing artwork configured in level set config from default */
658       skip_setup_artwork = TRUE;
659     }
660   }
661
662   if (!skip_setup_artwork)
663   {
664     /* 3rd try: look for special artwork in configured artwork directory */
665     filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
666     if (fileExists(filename))
667       return filename;
668
669     free(filename);
670   }
671
672   /* 4th try: look for default artwork in new default artwork directory */
673   filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
674   if (fileExists(filename))
675     return filename;
676
677   free(filename);
678
679   /* 5th try: look for default artwork in old default artwork directory */
680   filename = getPath2(options.music_directory, basename);
681   if (fileExists(filename))
682     return filename;
683
684   return NULL;          /* cannot find specified artwork file anywhere */
685 }
686
687 char *getCustomArtworkFilename(char *basename, int type)
688 {
689   if (type == ARTWORK_TYPE_GRAPHICS)
690     return getCustomImageFilename(basename);
691   else if (type == ARTWORK_TYPE_SOUNDS)
692     return getCustomSoundFilename(basename);
693   else if (type == ARTWORK_TYPE_MUSIC)
694     return getCustomMusicFilename(basename);
695   else
696     return UNDEFINED_FILENAME;
697 }
698
699 char *getCustomArtworkConfigFilename(int type)
700 {
701   return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
702 }
703
704 char *getCustomArtworkLevelConfigFilename(int type)
705 {
706   static char *filename = NULL;
707
708   checked_free(filename);
709
710   filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
711
712   return filename;
713 }
714
715 char *getCustomMusicDirectory(void)
716 {
717   static char *directory = NULL;
718   boolean skip_setup_artwork = FALSE;
719
720   checked_free(directory);
721
722   if (!setup.override_level_music)
723   {
724     /* 1st try: look for special artwork in current level series directory */
725     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
726     if (fileExists(directory))
727       return directory;
728
729     free(directory);
730
731     /* check if there is special artwork configured in level series config */
732     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
733     {
734       /* 2nd try: look for special artwork configured in level series config */
735       directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
736       if (fileExists(directory))
737         return directory;
738
739       free(directory);
740
741       /* take missing artwork configured in level set config from default */
742       skip_setup_artwork = TRUE;
743     }
744   }
745
746   if (!skip_setup_artwork)
747   {
748     /* 3rd try: look for special artwork in configured artwork directory */
749     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
750     if (fileExists(directory))
751       return directory;
752
753     free(directory);
754   }
755
756   /* 4th try: look for default artwork in new default artwork directory */
757   directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
758   if (fileExists(directory))
759     return directory;
760
761   free(directory);
762
763   /* 5th try: look for default artwork in old default artwork directory */
764   directory = getStringCopy(options.music_directory);
765   if (fileExists(directory))
766     return directory;
767
768   return NULL;          /* cannot find specified artwork file anywhere */
769 }
770
771 void InitTapeDirectory(char *level_subdir)
772 {
773   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
774   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
775   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
776 }
777
778 void InitScoreDirectory(char *level_subdir)
779 {
780   createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
781   createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
782   createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
783 }
784
785 static void SaveUserLevelInfo();
786
787 void InitUserLevelDirectory(char *level_subdir)
788 {
789   if (access(getUserLevelDir(level_subdir), F_OK) != 0)
790   {
791     createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
792     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
793     createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
794
795     SaveUserLevelInfo();
796   }
797 }
798
799 void InitLevelSetupDirectory(char *level_subdir)
800 {
801   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
802   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
803   createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
804 }
805
806
807 /* ------------------------------------------------------------------------- */
808 /* some functions to handle lists of level directories                       */
809 /* ------------------------------------------------------------------------- */
810
811 TreeInfo *newTreeInfo()
812 {
813   return checked_calloc(sizeof(TreeInfo));
814 }
815
816 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
817 {
818   node_new->next = *node_first;
819   *node_first = node_new;
820 }
821
822 int numTreeInfo(TreeInfo *node)
823 {
824   int num = 0;
825
826   while (node)
827   {
828     num++;
829     node = node->next;
830   }
831
832   return num;
833 }
834
835 boolean validLevelSeries(TreeInfo *node)
836 {
837   return (node != NULL && !node->node_group && !node->parent_link);
838 }
839
840 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
841 {
842   if (node == NULL)
843     return NULL;
844
845   if (node->node_group)         /* enter level group (step down into tree) */
846     return getFirstValidTreeInfoEntry(node->node_group);
847   else if (node->parent_link)   /* skip start entry of level group */
848   {
849     if (node->next)             /* get first real level series entry */
850       return getFirstValidTreeInfoEntry(node->next);
851     else                        /* leave empty level group and go on */
852       return getFirstValidTreeInfoEntry(node->node_parent->next);
853   }
854   else                          /* this seems to be a regular level series */
855     return node;
856 }
857
858 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
859 {
860   if (node == NULL)
861     return NULL;
862
863   if (node->node_parent == NULL)                /* top level group */
864     return *node->node_top;
865   else                                          /* sub level group */
866     return node->node_parent->node_group;
867 }
868
869 int numTreeInfoInGroup(TreeInfo *node)
870 {
871   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
872 }
873
874 int posTreeInfo(TreeInfo *node)
875 {
876   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
877   int pos = 0;
878
879   while (node_cmp)
880   {
881     if (node_cmp == node)
882       return pos;
883
884     pos++;
885     node_cmp = node_cmp->next;
886   }
887
888   return 0;
889 }
890
891 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
892 {
893   TreeInfo *node_default = node;
894   int pos_cmp = 0;
895
896   while (node)
897   {
898     if (pos_cmp == pos)
899       return node;
900
901     pos_cmp++;
902     node = node->next;
903   }
904
905   return node_default;
906 }
907
908 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
909 {
910   if (identifier == NULL)
911     return NULL;
912
913   while (node)
914   {
915     if (node->node_group)
916     {
917       TreeInfo *node_group;
918
919       node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
920
921       if (node_group)
922         return node_group;
923     }
924     else if (!node->parent_link)
925     {
926       if (strcmp(identifier, node->identifier) == 0)
927         return node;
928     }
929
930     node = node->next;
931   }
932
933   return NULL;
934 }
935
936 void dumpTreeInfo(TreeInfo *node, int depth)
937 {
938   int i;
939
940   printf("Dumping TreeInfo:\n");
941
942   while (node)
943   {
944     for (i = 0; i < (depth + 1) * 3; i++)
945       printf(" ");
946
947 #if 1
948     printf("filename == '%s' ['%s', '%s'] [%d])\n",
949            node->filename, node->fullpath, node->basepath, node->user_defined);
950 #else
951     printf("filename == '%s' (%s) [%s] (%d)\n",
952            node->filename, node->name, node->identifier, node->sort_priority);
953 #endif
954
955     if (node->node_group != NULL)
956       dumpTreeInfo(node->node_group, depth + 1);
957
958     node = node->next;
959   }
960 }
961
962 void sortTreeInfo(TreeInfo **node_first,
963                   int (*compare_function)(const void *, const void *))
964 {
965   int num_nodes = numTreeInfo(*node_first);
966   TreeInfo **sort_array;
967   TreeInfo *node = *node_first;
968   int i = 0;
969
970   if (num_nodes == 0)
971     return;
972
973   /* allocate array for sorting structure pointers */
974   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
975
976   /* writing structure pointers to sorting array */
977   while (i < num_nodes && node)         /* double boundary check... */
978   {
979     sort_array[i] = node;
980
981     i++;
982     node = node->next;
983   }
984
985   /* sorting the structure pointers in the sorting array */
986   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
987         compare_function);
988
989   /* update the linkage of list elements with the sorted node array */
990   for (i = 0; i < num_nodes - 1; i++)
991     sort_array[i]->next = sort_array[i + 1];
992   sort_array[num_nodes - 1]->next = NULL;
993
994   /* update the linkage of the main list anchor pointer */
995   *node_first = sort_array[0];
996
997   free(sort_array);
998
999   /* now recursively sort the level group structures */
1000   node = *node_first;
1001   while (node)
1002   {
1003     if (node->node_group != NULL)
1004       sortTreeInfo(&node->node_group, compare_function);
1005
1006     node = node->next;
1007   }
1008 }
1009
1010
1011 /* ========================================================================= */
1012 /* some stuff from "files.c"                                                 */
1013 /* ========================================================================= */
1014
1015 #if defined(PLATFORM_WIN32)
1016 #ifndef S_IRGRP
1017 #define S_IRGRP S_IRUSR
1018 #endif
1019 #ifndef S_IROTH
1020 #define S_IROTH S_IRUSR
1021 #endif
1022 #ifndef S_IWGRP
1023 #define S_IWGRP S_IWUSR
1024 #endif
1025 #ifndef S_IWOTH
1026 #define S_IWOTH S_IWUSR
1027 #endif
1028 #ifndef S_IXGRP
1029 #define S_IXGRP S_IXUSR
1030 #endif
1031 #ifndef S_IXOTH
1032 #define S_IXOTH S_IXUSR
1033 #endif
1034 #ifndef S_IRWXG
1035 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1036 #endif
1037 #ifndef S_ISGID
1038 #define S_ISGID 0
1039 #endif
1040 #endif  /* PLATFORM_WIN32 */
1041
1042 /* file permissions for newly written files */
1043 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1044 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1045 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1046
1047 #define MODE_W_PRIVATE          (S_IWUSR)
1048 #define MODE_W_PUBLIC           (S_IWUSR | S_IWGRP)
1049 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1050
1051 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1052 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1053
1054 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1055 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC)
1056
1057 char *getUserDataDir(void)
1058 {
1059   static char *userdata_dir = NULL;
1060
1061   if (userdata_dir == NULL)
1062     userdata_dir = getPath2(getHomeDir(), program.userdata_directory);
1063
1064   return userdata_dir;
1065 }
1066
1067 char *getCommonDataDir(void)
1068 {
1069   static char *common_data_dir = NULL;
1070
1071 #if defined(PLATFORM_WIN32)
1072   if (common_data_dir == NULL)
1073   {
1074     char *dir = checked_malloc(MAX_PATH + 1);
1075
1076     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1077         && strcmp(dir, "") != 0)        /* empty for Windows 95/98 */
1078       common_data_dir = getPath2(dir, program.userdata_directory);
1079     else
1080       common_data_dir = options.rw_base_directory;
1081   }
1082 #else
1083   if (common_data_dir == NULL)
1084     common_data_dir = options.rw_base_directory;
1085 #endif
1086
1087   return common_data_dir;
1088 }
1089
1090 char *getSetupDir()
1091 {
1092   return getUserDataDir();
1093 }
1094
1095 static mode_t posix_umask(mode_t mask)
1096 {
1097 #if defined(PLATFORM_UNIX)
1098   return umask(mask);
1099 #else
1100   return 0;
1101 #endif
1102 }
1103
1104 static int posix_mkdir(const char *pathname, mode_t mode)
1105 {
1106 #if defined(PLATFORM_WIN32)
1107   return mkdir(pathname);
1108 #else
1109   return mkdir(pathname, mode);
1110 #endif
1111 }
1112
1113 void createDirectory(char *dir, char *text, int permission_class)
1114 {
1115   /* leave "other" permissions in umask untouched, but ensure group parts
1116      of USERDATA_DIR_MODE are not masked */
1117   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1118                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1119   mode_t normal_umask = posix_umask(0);
1120   mode_t group_umask = ~(dir_mode & S_IRWXG);
1121   posix_umask(normal_umask & group_umask);
1122
1123   if (access(dir, F_OK) != 0)
1124     if (posix_mkdir(dir, dir_mode) != 0)
1125       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1126
1127   posix_umask(normal_umask);            /* reset normal umask */
1128 }
1129
1130 void InitUserDataDirectory()
1131 {
1132   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
1133 }
1134
1135 void SetFilePermissions(char *filename, int permission_class)
1136 {
1137   chmod(filename, (permission_class == PERMS_PRIVATE ?
1138                    FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1139 }
1140
1141 char *getCookie(char *file_type)
1142 {
1143   static char cookie[MAX_COOKIE_LEN + 1];
1144
1145   if (strlen(program.cookie_prefix) + 1 +
1146       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1147     return "[COOKIE ERROR]";    /* should never happen */
1148
1149   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1150           program.cookie_prefix, file_type,
1151           program.version_major, program.version_minor);
1152
1153   return cookie;
1154 }
1155
1156 int getFileVersionFromCookieString(const char *cookie)
1157 {
1158   const char *ptr_cookie1, *ptr_cookie2;
1159   const char *pattern1 = "_FILE_VERSION_";
1160   const char *pattern2 = "?.?";
1161   const int len_cookie = strlen(cookie);
1162   const int len_pattern1 = strlen(pattern1);
1163   const int len_pattern2 = strlen(pattern2);
1164   const int len_pattern = len_pattern1 + len_pattern2;
1165   int version_major, version_minor;
1166
1167   if (len_cookie <= len_pattern)
1168     return -1;
1169
1170   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1171   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1172
1173   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1174     return -1;
1175
1176   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1177       ptr_cookie2[1] != '.' ||
1178       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1179     return -1;
1180
1181   version_major = ptr_cookie2[0] - '0';
1182   version_minor = ptr_cookie2[2] - '0';
1183
1184   return VERSION_IDENT(version_major, version_minor, 0, 0);
1185 }
1186
1187 boolean checkCookieString(const char *cookie, const char *template)
1188 {
1189   const char *pattern = "_FILE_VERSION_?.?";
1190   const int len_cookie = strlen(cookie);
1191   const int len_template = strlen(template);
1192   const int len_pattern = strlen(pattern);
1193
1194   if (len_cookie != len_template)
1195     return FALSE;
1196
1197   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1198     return FALSE;
1199
1200   return TRUE;
1201 }
1202
1203 /* ------------------------------------------------------------------------- */
1204 /* setup file list and hash handling functions                               */
1205 /* ------------------------------------------------------------------------- */
1206
1207 char *getFormattedSetupEntry(char *token, char *value)
1208 {
1209   int i;
1210   static char entry[MAX_LINE_LEN];
1211
1212   /* if value is an empty string, just return token without value */
1213   if (*value == '\0')
1214     return token;
1215
1216   /* start with the token and some spaces to format output line */
1217   sprintf(entry, "%s:", token);
1218   for (i = strlen(entry); i < TOKEN_VALUE_POSITION; i++)
1219     strcat(entry, " ");
1220
1221   /* continue with the token's value */
1222   strcat(entry, value);
1223
1224   return entry;
1225 }
1226
1227 SetupFileList *newSetupFileList(char *token, char *value)
1228 {
1229   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1230
1231   new->token = getStringCopy(token);
1232   new->value = getStringCopy(value);
1233
1234   new->next = NULL;
1235
1236   return new;
1237 }
1238
1239 void freeSetupFileList(SetupFileList *list)
1240 {
1241   if (list == NULL)
1242     return;
1243
1244   checked_free(list->token);
1245   checked_free(list->value);
1246
1247   if (list->next)
1248     freeSetupFileList(list->next);
1249
1250   free(list);
1251 }
1252
1253 char *getListEntry(SetupFileList *list, char *token)
1254 {
1255   if (list == NULL)
1256     return NULL;
1257
1258   if (strcmp(list->token, token) == 0)
1259     return list->value;
1260   else
1261     return getListEntry(list->next, token);
1262 }
1263
1264 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1265 {
1266   if (list == NULL)
1267     return NULL;
1268
1269   if (strcmp(list->token, token) == 0)
1270   {
1271     checked_free(list->value);
1272
1273     list->value = getStringCopy(value);
1274
1275     return list;
1276   }
1277   else if (list->next == NULL)
1278     return (list->next = newSetupFileList(token, value));
1279   else
1280     return setListEntry(list->next, token, value);
1281 }
1282
1283 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1284 {
1285   if (list == NULL)
1286     return NULL;
1287
1288   if (list->next == NULL)
1289     return (list->next = newSetupFileList(token, value));
1290   else
1291     return addListEntry(list->next, token, value);
1292 }
1293
1294 #ifdef DEBUG
1295 static void printSetupFileList(SetupFileList *list)
1296 {
1297   if (!list)
1298     return;
1299
1300   printf("token: '%s'\n", list->token);
1301   printf("value: '%s'\n", list->value);
1302
1303   printSetupFileList(list->next);
1304 }
1305 #endif
1306
1307 #ifdef DEBUG
1308 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1309 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1310 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1311 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1312 #else
1313 #define insert_hash_entry hashtable_insert
1314 #define search_hash_entry hashtable_search
1315 #define change_hash_entry hashtable_change
1316 #define remove_hash_entry hashtable_remove
1317 #endif
1318
1319 static unsigned int get_hash_from_key(void *key)
1320 {
1321   /*
1322     djb2
1323
1324     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1325     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1326     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1327     it works better than many other constants, prime or not) has never been
1328     adequately explained.
1329
1330     If you just want to have a good hash function, and cannot wait, djb2
1331     is one of the best string hash functions i know. It has excellent
1332     distribution and speed on many different sets of keys and table sizes.
1333     You are not likely to do better with one of the "well known" functions
1334     such as PJW, K&R, etc.
1335
1336     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1337   */
1338
1339   char *str = (char *)key;
1340   unsigned int hash = 5381;
1341   int c;
1342
1343   while ((c = *str++))
1344     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1345
1346   return hash;
1347 }
1348
1349 static int keys_are_equal(void *key1, void *key2)
1350 {
1351   return (strcmp((char *)key1, (char *)key2) == 0);
1352 }
1353
1354 SetupFileHash *newSetupFileHash()
1355 {
1356   SetupFileHash *new_hash =
1357     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1358
1359   if (new_hash == NULL)
1360     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1361
1362   return new_hash;
1363 }
1364
1365 void freeSetupFileHash(SetupFileHash *hash)
1366 {
1367   if (hash == NULL)
1368     return;
1369
1370   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1371 }
1372
1373 char *getHashEntry(SetupFileHash *hash, char *token)
1374 {
1375   if (hash == NULL)
1376     return NULL;
1377
1378   return search_hash_entry(hash, token);
1379 }
1380
1381 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1382 {
1383   char *value_copy;
1384
1385   if (hash == NULL)
1386     return;
1387
1388   value_copy = getStringCopy(value);
1389
1390   /* change value; if it does not exist, insert it as new */
1391   if (!change_hash_entry(hash, token, value_copy))
1392     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1393       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1394 }
1395
1396 char *removeHashEntry(SetupFileHash *hash, char *token)
1397 {
1398   if (hash == NULL)
1399     return NULL;
1400
1401   return remove_hash_entry(hash, token);
1402 }
1403
1404 #if 0
1405 #ifdef DEBUG
1406 static void printSetupFileHash(SetupFileHash *hash)
1407 {
1408   BEGIN_HASH_ITERATION(hash, itr)
1409   {
1410     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1411     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1412   }
1413   END_HASH_ITERATION(hash, itr)
1414 }
1415 #endif
1416 #endif
1417
1418 static void *loadSetupFileData(char *filename, boolean use_hash)
1419 {
1420   char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1421   char *token, *value, *line_ptr;
1422   void *setup_file_data, *insert_ptr = NULL;
1423   boolean read_continued_line = FALSE;
1424   FILE *file;
1425
1426   if (use_hash)
1427     setup_file_data = newSetupFileHash();
1428   else
1429     insert_ptr = setup_file_data = newSetupFileList("", "");
1430
1431   if (!(file = fopen(filename, MODE_READ)))
1432   {
1433     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1434     return NULL;
1435   }
1436
1437   while (!feof(file))
1438   {
1439     /* read next line of input file */
1440     if (!fgets(line, MAX_LINE_LEN, file))
1441       break;
1442
1443     /* cut trailing newline or carriage return */
1444     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1445       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1446         *line_ptr = '\0';
1447
1448     if (read_continued_line)
1449     {
1450       /* cut leading whitespaces from input line */
1451       for (line_ptr = line; *line_ptr; line_ptr++)
1452         if (*line_ptr != ' ' && *line_ptr != '\t')
1453           break;
1454
1455       /* append new line to existing line, if there is enough space */
1456       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1457         strcat(previous_line, line_ptr);
1458
1459       strcpy(line, previous_line);      /* copy storage buffer to line */
1460
1461       read_continued_line = FALSE;
1462     }
1463
1464     /* if the last character is '\', continue at next line */
1465     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1466     {
1467       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
1468       strcpy(previous_line, line);      /* copy line to storage buffer */
1469
1470       read_continued_line = TRUE;
1471
1472       continue;
1473     }
1474
1475     /* cut trailing comment from input line */
1476     for (line_ptr = line; *line_ptr; line_ptr++)
1477     {
1478       if (*line_ptr == '#')
1479       {
1480         *line_ptr = '\0';
1481         break;
1482       }
1483     }
1484
1485     /* cut trailing whitespaces from input line */
1486     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1487       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1488         *line_ptr = '\0';
1489
1490     /* ignore empty lines */
1491     if (*line == '\0')
1492       continue;
1493
1494     /* cut leading whitespaces from token */
1495     for (token = line; *token; token++)
1496       if (*token != ' ' && *token != '\t')
1497         break;
1498
1499     /* start with empty value as reliable default */
1500     value = "";
1501
1502     /* find end of token to determine start of value */
1503     for (line_ptr = token; *line_ptr; line_ptr++)
1504     {
1505       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1506       {
1507         *line_ptr = '\0';               /* terminate token string */
1508         value = line_ptr + 1;           /* set beginning of value */
1509
1510         break;
1511       }
1512     }
1513
1514     /* cut leading whitespaces from value */
1515     for (; *value; value++)
1516       if (*value != ' ' && *value != '\t')
1517         break;
1518
1519 #if 0
1520     if (*value == '\0')
1521       value = "true";   /* treat tokens without value as "true" */
1522 #endif
1523
1524     if (*token)
1525     {
1526       if (use_hash)
1527         setHashEntry((SetupFileHash *)setup_file_data, token, value);
1528       else
1529         insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1530     }
1531   }
1532
1533   fclose(file);
1534
1535   if (use_hash)
1536   {
1537     if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1538       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1539   }
1540   else
1541   {
1542     SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1543     SetupFileList *first_valid_list_entry = setup_file_list->next;
1544
1545     /* free empty list header */
1546     setup_file_list->next = NULL;
1547     freeSetupFileList(setup_file_list);
1548     setup_file_data = first_valid_list_entry;
1549
1550     if (first_valid_list_entry == NULL)
1551       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1552   }
1553
1554   return setup_file_data;
1555 }
1556
1557 SetupFileList *loadSetupFileList(char *filename)
1558 {
1559   return (SetupFileList *)loadSetupFileData(filename, FALSE);
1560 }
1561
1562 SetupFileHash *loadSetupFileHash(char *filename)
1563 {
1564   return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1565 }
1566
1567 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1568                                   char *identifier)
1569 {
1570   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1571
1572   if (value == NULL)
1573     Error(ERR_WARN, "configuration file has no file identifier");
1574   else if (!checkCookieString(value, identifier))
1575     Error(ERR_WARN, "configuration file has wrong file identifier");
1576 }
1577
1578
1579 /* ========================================================================= */
1580 /* setup file stuff                                                          */
1581 /* ========================================================================= */
1582
1583 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1584 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1585 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1586
1587 /* level directory info */
1588 #define LEVELINFO_TOKEN_IDENTIFIER      0
1589 #define LEVELINFO_TOKEN_NAME            1
1590 #define LEVELINFO_TOKEN_NAME_SORTING    2
1591 #define LEVELINFO_TOKEN_AUTHOR          3
1592 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1593 #define LEVELINFO_TOKEN_LEVELS          5
1594 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
1595 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
1596 #define LEVELINFO_TOKEN_LATEST_ENGINE   8
1597 #define LEVELINFO_TOKEN_LEVEL_GROUP     9
1598 #define LEVELINFO_TOKEN_READONLY        10
1599 #define LEVELINFO_TOKEN_GRAPHICS_SET    11
1600 #define LEVELINFO_TOKEN_SOUNDS_SET      12
1601 #define LEVELINFO_TOKEN_MUSIC_SET       13
1602
1603 #define NUM_LEVELINFO_TOKENS            14
1604
1605 static LevelDirTree ldi;
1606
1607 static struct TokenInfo levelinfo_tokens[] =
1608 {
1609   /* level directory info */
1610   { TYPE_STRING,  &ldi.identifier,      "identifier"    },
1611   { TYPE_STRING,  &ldi.name,            "name"          },
1612   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1613   { TYPE_STRING,  &ldi.author,          "author"        },
1614   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1615   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1616   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1617   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1618   { TYPE_BOOLEAN, &ldi.latest_engine,   "latest_engine" },
1619   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1620   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      },
1621   { TYPE_STRING,  &ldi.graphics_set,    "graphics_set"  },
1622   { TYPE_STRING,  &ldi.sounds_set,      "sounds_set"    },
1623   { TYPE_STRING,  &ldi.music_set,       "music_set"     }
1624 };
1625
1626 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1627 {
1628   ldi->type = type;
1629
1630   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1631                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1632                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1633                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1634                    NULL);
1635
1636   ldi->node_parent = NULL;
1637   ldi->node_group = NULL;
1638   ldi->next = NULL;
1639
1640   ldi->cl_first = -1;
1641   ldi->cl_cursor = -1;
1642
1643   ldi->filename = NULL;
1644   ldi->fullpath = NULL;
1645   ldi->basepath = NULL;
1646   ldi->identifier = NULL;
1647   ldi->name = getStringCopy(ANONYMOUS_NAME);
1648   ldi->name_sorting = NULL;
1649   ldi->author = getStringCopy(ANONYMOUS_NAME);
1650
1651   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1652   ldi->latest_engine = FALSE;                   /* default: get from level */
1653   ldi->parent_link = FALSE;
1654   ldi->user_defined = FALSE;
1655   ldi->color = 0;
1656   ldi->class_desc = NULL;
1657
1658   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1659   {
1660     ldi->imported_from = NULL;
1661
1662     ldi->graphics_set = NULL;
1663     ldi->sounds_set = NULL;
1664     ldi->music_set = NULL;
1665     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1666     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1667     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1668
1669     ldi->levels = 0;
1670     ldi->first_level = 0;
1671     ldi->last_level = 0;
1672     ldi->level_group = FALSE;
1673     ldi->handicap_level = 0;
1674     ldi->readonly = TRUE;
1675   }
1676 }
1677
1678 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1679 {
1680   if (parent == NULL)
1681   {
1682     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1683
1684     setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
1685
1686     return;
1687   }
1688
1689 #if 1
1690   /* copy all values from the parent structure */
1691
1692   ldi->type = parent->type;
1693
1694   ldi->node_top = parent->node_top;
1695   ldi->node_parent = parent;
1696   ldi->node_group = NULL;
1697   ldi->next = NULL;
1698
1699   ldi->cl_first = -1;
1700   ldi->cl_cursor = -1;
1701
1702   ldi->filename = NULL;
1703   ldi->fullpath = NULL;
1704   ldi->basepath = NULL;
1705   ldi->identifier = NULL;
1706   ldi->name = getStringCopy(ANONYMOUS_NAME);
1707   ldi->name_sorting = NULL;
1708   ldi->author = getStringCopy(parent->author);
1709
1710   ldi->sort_priority = parent->sort_priority;
1711   ldi->latest_engine = parent->latest_engine;
1712   ldi->parent_link = FALSE;
1713   ldi->user_defined = parent->user_defined;
1714   ldi->color = parent->color;
1715   ldi->class_desc = getStringCopy(parent->class_desc);
1716
1717   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1718   {
1719     ldi->imported_from = getStringCopy(parent->imported_from);
1720
1721     ldi->graphics_set = NULL;
1722     ldi->sounds_set = NULL;
1723     ldi->music_set = NULL;
1724     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1725     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1726     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1727
1728     ldi->levels = 0;
1729     ldi->first_level = 0;
1730     ldi->last_level = 0;
1731     ldi->level_group = FALSE;
1732     ldi->handicap_level = 0;
1733     ldi->readonly = TRUE;
1734   }
1735
1736
1737 #else
1738
1739   /* first copy all values from the parent structure ... */
1740   *ldi = *parent;
1741
1742   /* ... then set all fields to default that cannot be inherited from parent.
1743      This is especially important for all those fields that can be set from
1744      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1745      calls 'free()' for all already set token values which requires that no
1746      other structure's pointer may point to them!
1747   */
1748
1749   ldi->filename = NULL;
1750   ldi->fullpath = NULL;
1751   ldi->basepath = NULL;
1752   ldi->identifier = NULL;
1753   ldi->name = getStringCopy(ANONYMOUS_NAME);
1754   ldi->name_sorting = NULL;
1755   ldi->author = getStringCopy(parent->author);
1756
1757   ldi->imported_from = getStringCopy(parent->imported_from);
1758   ldi->class_desc = getStringCopy(parent->class_desc);
1759
1760   ldi->graphics_set = NULL;
1761   ldi->sounds_set = NULL;
1762   ldi->music_set = NULL;
1763   ldi->graphics_path = NULL;
1764   ldi->sounds_path = NULL;
1765   ldi->music_path = NULL;
1766
1767   ldi->level_group = FALSE;
1768   ldi->parent_link = FALSE;
1769
1770   ldi->node_top = parent->node_top;
1771   ldi->node_parent = parent;
1772   ldi->node_group = NULL;
1773   ldi->next = NULL;
1774
1775 #endif
1776 }
1777
1778 static void freeTreeInfo(TreeInfo *ldi)
1779 {
1780   checked_free(ldi->filename);
1781   checked_free(ldi->fullpath);
1782   checked_free(ldi->basepath);
1783   checked_free(ldi->identifier);
1784
1785   checked_free(ldi->name);
1786   checked_free(ldi->name_sorting);
1787   checked_free(ldi->author);
1788
1789   checked_free(ldi->class_desc);
1790
1791   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1792   {
1793     checked_free(ldi->graphics_set);
1794     checked_free(ldi->sounds_set);
1795     checked_free(ldi->music_set);
1796
1797     checked_free(ldi->graphics_path);
1798     checked_free(ldi->sounds_path);
1799     checked_free(ldi->music_path);
1800   }
1801 }
1802
1803 void setSetupInfo(struct TokenInfo *token_info,
1804                   int token_nr, char *token_value)
1805 {
1806   int token_type = token_info[token_nr].type;
1807   void *setup_value = token_info[token_nr].value;
1808
1809   if (token_value == NULL)
1810     return;
1811
1812   /* set setup field to corresponding token value */
1813   switch (token_type)
1814   {
1815     case TYPE_BOOLEAN:
1816     case TYPE_SWITCH:
1817       *(boolean *)setup_value = get_boolean_from_string(token_value);
1818       break;
1819
1820     case TYPE_KEY:
1821       *(Key *)setup_value = getKeyFromKeyName(token_value);
1822       break;
1823
1824     case TYPE_KEY_X11:
1825       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1826       break;
1827
1828     case TYPE_INTEGER:
1829       *(int *)setup_value = get_integer_from_string(token_value);
1830       break;
1831
1832     case TYPE_STRING:
1833       checked_free(*(char **)setup_value);
1834       *(char **)setup_value = getStringCopy(token_value);
1835       break;
1836
1837     default:
1838       break;
1839   }
1840 }
1841
1842 static int compareTreeInfoEntries(const void *object1, const void *object2)
1843 {
1844   const TreeInfo *entry1 = *((TreeInfo **)object1);
1845   const TreeInfo *entry2 = *((TreeInfo **)object2);
1846   int class_sorting1, class_sorting2;
1847   int compare_result;
1848
1849   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1850   {
1851     class_sorting1 = LEVELSORTING(entry1);
1852     class_sorting2 = LEVELSORTING(entry2);
1853   }
1854   else
1855   {
1856     class_sorting1 = ARTWORKSORTING(entry1);
1857     class_sorting2 = ARTWORKSORTING(entry2);
1858   }
1859
1860   if (entry1->parent_link || entry2->parent_link)
1861     compare_result = (entry1->parent_link ? -1 : +1);
1862   else if (entry1->sort_priority == entry2->sort_priority)
1863   {
1864     char *name1 = getStringToLower(entry1->name_sorting);
1865     char *name2 = getStringToLower(entry2->name_sorting);
1866
1867     compare_result = strcmp(name1, name2);
1868
1869     free(name1);
1870     free(name2);
1871   }
1872   else if (class_sorting1 == class_sorting2)
1873     compare_result = entry1->sort_priority - entry2->sort_priority;
1874   else
1875     compare_result = class_sorting1 - class_sorting2;
1876
1877   return compare_result;
1878 }
1879
1880 static void createParentTreeInfoNode(TreeInfo *node_parent)
1881 {
1882   TreeInfo *ti_new;
1883
1884   if (node_parent == NULL)
1885     return;
1886
1887   ti_new = newTreeInfo();
1888   setTreeInfoToDefaults(ti_new, node_parent->type);
1889
1890   ti_new->node_parent = node_parent;
1891   ti_new->parent_link = TRUE;
1892
1893 #if 1
1894   setString(&ti_new->identifier, node_parent->identifier);
1895   setString(&ti_new->name, ".. (parent directory)");
1896   setString(&ti_new->name_sorting, ti_new->name);
1897
1898   setString(&ti_new->filename, "..");
1899   setString(&ti_new->fullpath, node_parent->fullpath);
1900
1901   ti_new->sort_priority = node_parent->sort_priority;
1902   ti_new->latest_engine = node_parent->latest_engine;
1903
1904   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
1905 #else
1906   ti_new->identifier = getStringCopy(node_parent->identifier);
1907   ti_new->name = ".. (parent directory)";
1908   ti_new->name_sorting = getStringCopy(ti_new->name);
1909
1910   ti_new->filename = "..";
1911   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1912
1913   ti_new->sort_priority = node_parent->sort_priority;
1914   ti_new->latest_engine = node_parent->latest_engine;
1915
1916   ti_new->class_desc = getLevelClassDescription(ti_new);
1917 #endif
1918
1919   pushTreeInfo(&node_parent->node_group, ti_new);
1920 }
1921
1922 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1923 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1924
1925 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1926                                           TreeInfo *node_parent,
1927                                           char *level_directory,
1928                                           char *directory_name)
1929 {
1930   char *directory_path = getPath2(level_directory, directory_name);
1931   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1932   SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1933   LevelDirTree *leveldir_new = NULL;
1934   int i;
1935
1936   if (setup_file_hash == NULL)
1937   {
1938     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1939
1940     free(directory_path);
1941     free(filename);
1942
1943     return FALSE;
1944   }
1945
1946   leveldir_new = newTreeInfo();
1947
1948   if (node_parent)
1949     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1950   else
1951     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1952
1953   leveldir_new->filename = getStringCopy(directory_name);
1954
1955   checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1956
1957   /* set all structure fields according to the token/value pairs */
1958   ldi = *leveldir_new;
1959   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
1960     setSetupInfo(levelinfo_tokens, i,
1961                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1962   *leveldir_new = ldi;
1963
1964 #if 1
1965   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1966     setString(&leveldir_new->name, leveldir_new->filename);
1967 #else
1968   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1969   {
1970     free(leveldir_new->name);
1971     leveldir_new->name = getStringCopy(leveldir_new->filename);
1972   }
1973 #endif
1974
1975   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1976
1977   if (leveldir_new->identifier == NULL)
1978     leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1979
1980   if (leveldir_new->name_sorting == NULL)
1981     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1982
1983   if (node_parent == NULL)              /* top level group */
1984   {
1985     leveldir_new->basepath = getStringCopy(level_directory);
1986     leveldir_new->fullpath = getStringCopy(leveldir_new->filename);
1987   }
1988   else                                  /* sub level group */
1989   {
1990     leveldir_new->basepath = getStringCopy(node_parent->basepath);
1991     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1992   }
1993
1994   if (leveldir_new->levels < 1)
1995     leveldir_new->levels = 1;
1996
1997   leveldir_new->last_level =
1998     leveldir_new->first_level + leveldir_new->levels - 1;
1999
2000 #if 1
2001   leveldir_new->user_defined =
2002     (strcmp(leveldir_new->basepath, options.level_directory) != 0);
2003 #else
2004   leveldir_new->user_defined =
2005     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
2006 #endif
2007
2008   leveldir_new->color = LEVELCOLOR(leveldir_new);
2009 #if 1
2010   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2011 #else
2012   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
2013 #endif
2014
2015   leveldir_new->handicap_level =        /* set handicap to default value */
2016     (leveldir_new->user_defined ?
2017      leveldir_new->last_level :
2018      leveldir_new->first_level);
2019
2020   pushTreeInfo(node_first, leveldir_new);
2021
2022   freeSetupFileHash(setup_file_hash);
2023
2024   if (leveldir_new->level_group)
2025   {
2026     /* create node to link back to current level directory */
2027     createParentTreeInfoNode(leveldir_new);
2028
2029     /* step into sub-directory and look for more level series */
2030     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2031                               leveldir_new, directory_path);
2032   }
2033
2034   free(directory_path);
2035   free(filename);
2036
2037   return TRUE;
2038 }
2039
2040 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2041                                       TreeInfo *node_parent,
2042                                       char *level_directory)
2043 {
2044   DIR *dir;
2045   struct dirent *dir_entry;
2046   boolean valid_entry_found = FALSE;
2047
2048   if ((dir = opendir(level_directory)) == NULL)
2049   {
2050     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2051     return;
2052   }
2053
2054   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2055   {
2056     struct stat file_status;
2057     char *directory_name = dir_entry->d_name;
2058     char *directory_path = getPath2(level_directory, directory_name);
2059
2060     /* skip entries for current and parent directory */
2061     if (strcmp(directory_name, ".")  == 0 ||
2062         strcmp(directory_name, "..") == 0)
2063     {
2064       free(directory_path);
2065       continue;
2066     }
2067
2068     /* find out if directory entry is itself a directory */
2069     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2070         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2071     {
2072       free(directory_path);
2073       continue;
2074     }
2075
2076     free(directory_path);
2077
2078     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
2079         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
2080         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
2081       continue;
2082
2083     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2084                                                     level_directory,
2085                                                     directory_name);
2086   }
2087
2088   closedir(dir);
2089
2090   if (!valid_entry_found)
2091   {
2092     /* check if this directory directly contains a file "levelinfo.conf" */
2093     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2094                                                     level_directory, ".");
2095   }
2096
2097   if (!valid_entry_found)
2098     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2099           level_directory);
2100 }
2101
2102 void LoadLevelInfo()
2103 {
2104   InitUserLevelDirectory(getLoginName());
2105
2106   DrawInitText("Loading level series:", 120, FC_GREEN);
2107
2108   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2109   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2110
2111   /* before sorting, the first entries will be from the user directory */
2112   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2113
2114   if (leveldir_first == NULL)
2115     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2116
2117   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
2118
2119 #if 0
2120   dumpTreeInfo(leveldir_first, 0);
2121 #endif
2122 }
2123
2124 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2125                                               TreeInfo *node_parent,
2126                                               char *base_directory,
2127                                               char *directory_name, int type)
2128 {
2129   char *directory_path = getPath2(base_directory, directory_name);
2130   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2131   SetupFileHash *setup_file_hash = NULL;
2132   TreeInfo *artwork_new = NULL;
2133   int i;
2134
2135   if (access(filename, F_OK) == 0)              /* file exists */
2136     setup_file_hash = loadSetupFileHash(filename);
2137
2138   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2139   {
2140     DIR *dir;
2141     struct dirent *dir_entry;
2142     boolean valid_file_found = FALSE;
2143
2144     if ((dir = opendir(directory_path)) != NULL)
2145     {
2146       while ((dir_entry = readdir(dir)) != NULL)
2147       {
2148         char *entry_name = dir_entry->d_name;
2149
2150         if (FileIsArtworkType(entry_name, type))
2151         {
2152           valid_file_found = TRUE;
2153           break;
2154         }
2155       }
2156
2157       closedir(dir);
2158     }
2159
2160     if (!valid_file_found)
2161     {
2162       if (strcmp(directory_name, ".") != 0)
2163         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2164
2165       free(directory_path);
2166       free(filename);
2167
2168       return FALSE;
2169     }
2170   }
2171
2172   artwork_new = newTreeInfo();
2173
2174   if (node_parent)
2175     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2176   else
2177     setTreeInfoToDefaults(artwork_new, type);
2178
2179   artwork_new->filename = getStringCopy(directory_name);
2180
2181   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2182   {
2183 #if 0
2184     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
2185 #endif
2186
2187     /* set all structure fields according to the token/value pairs */
2188     ldi = *artwork_new;
2189     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2190       setSetupInfo(levelinfo_tokens, i,
2191                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2192     *artwork_new = ldi;
2193
2194 #if 1
2195     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2196       setString(&artwork_new->name, artwork_new->filename);
2197 #else
2198     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2199     {
2200       free(artwork_new->name);
2201       artwork_new->name = getStringCopy(artwork_new->filename);
2202     }
2203 #endif
2204
2205 #if 0
2206     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2207 #endif
2208
2209     if (artwork_new->identifier == NULL)
2210       artwork_new->identifier = getStringCopy(artwork_new->filename);
2211
2212     if (artwork_new->name_sorting == NULL)
2213       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2214   }
2215
2216   if (node_parent == NULL)              /* top level group */
2217   {
2218     artwork_new->basepath = getStringCopy(base_directory);
2219     artwork_new->fullpath = getStringCopy(artwork_new->filename);
2220   }
2221   else                                  /* sub level group */
2222   {
2223     artwork_new->basepath = getStringCopy(node_parent->basepath);
2224     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2225   }
2226
2227 #if 1
2228   artwork_new->user_defined =
2229     (strcmp(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)) != 0);
2230 #else
2231   artwork_new->user_defined =
2232     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
2233 #endif
2234
2235   /* (may use ".sort_priority" from "setup_file_hash" above) */
2236   artwork_new->color = ARTWORKCOLOR(artwork_new);
2237 #if 1
2238   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2239 #else
2240   artwork_new->class_desc = getLevelClassDescription(artwork_new);
2241 #endif
2242
2243   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2244   {
2245 #if 0
2246     if (artwork_new->name != NULL)
2247     {
2248       free(artwork_new->name);
2249       artwork_new->name = NULL;
2250     }
2251 #endif
2252
2253 #if 0
2254     if (artwork_new->identifier != NULL)
2255     {
2256       free(artwork_new->identifier);
2257       artwork_new->identifier = NULL;
2258     }
2259 #endif
2260
2261     if (strcmp(artwork_new->filename, ".") == 0)
2262     {
2263       if (artwork_new->user_defined)
2264       {
2265 #if 1
2266         setString(&artwork_new->identifier, "private");
2267 #else
2268         artwork_new->identifier = getStringCopy("private");
2269 #endif
2270         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2271       }
2272       else
2273       {
2274 #if 1
2275         setString(&artwork_new->identifier, "classic");
2276 #else
2277         artwork_new->identifier = getStringCopy("classic");
2278 #endif
2279         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2280       }
2281
2282       /* set to new values after changing ".sort_priority" */
2283       artwork_new->color = ARTWORKCOLOR(artwork_new);
2284 #if 1
2285       setString(&artwork_new->class_desc,
2286                 getLevelClassDescription(artwork_new));
2287 #else
2288       artwork_new->class_desc = getLevelClassDescription(artwork_new);
2289 #endif
2290     }
2291     else
2292     {
2293 #if 1
2294       setString(&artwork_new->identifier, artwork_new->filename);
2295 #else
2296       artwork_new->identifier = getStringCopy(artwork_new->filename);
2297 #endif
2298     }
2299
2300 #if 1
2301     setString(&artwork_new->name, artwork_new->identifier);
2302     setString(&artwork_new->name_sorting, artwork_new->name);
2303 #else
2304     artwork_new->name = getStringCopy(artwork_new->identifier);
2305     artwork_new->name_sorting = getStringCopy(artwork_new->name);
2306 #endif
2307   }
2308
2309   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2310
2311   pushTreeInfo(node_first, artwork_new);
2312
2313   freeSetupFileHash(setup_file_hash);
2314
2315   free(directory_path);
2316   free(filename);
2317
2318   return TRUE;
2319 }
2320
2321 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2322                                           TreeInfo *node_parent,
2323                                           char *base_directory, int type)
2324 {
2325   DIR *dir;
2326   struct dirent *dir_entry;
2327   boolean valid_entry_found = FALSE;
2328
2329   if ((dir = opendir(base_directory)) == NULL)
2330   {
2331     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2332       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2333     return;
2334   }
2335
2336   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2337   {
2338     struct stat file_status;
2339     char *directory_name = dir_entry->d_name;
2340     char *directory_path = getPath2(base_directory, directory_name);
2341
2342     /* skip entries for current and parent directory */
2343     if (strcmp(directory_name, ".")  == 0 ||
2344         strcmp(directory_name, "..") == 0)
2345     {
2346       free(directory_path);
2347       continue;
2348     }
2349
2350     /* find out if directory entry is itself a directory */
2351     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2352         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2353     {
2354       free(directory_path);
2355       continue;
2356     }
2357
2358     free(directory_path);
2359
2360     /* check if this directory contains artwork with or without config file */
2361     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2362                                                         base_directory,
2363                                                         directory_name, type);
2364   }
2365
2366   closedir(dir);
2367
2368   /* check if this directory directly contains artwork itself */
2369   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2370                                                       base_directory, ".",
2371                                                       type);
2372   if (!valid_entry_found)
2373     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2374           base_directory);
2375 }
2376
2377 static TreeInfo *getDummyArtworkInfo(int type)
2378 {
2379   /* this is only needed when there is completely no artwork available */
2380   TreeInfo *artwork_new = newTreeInfo();
2381
2382   setTreeInfoToDefaults(artwork_new, type);
2383
2384 #if 1
2385   setString(&artwork_new->filename, UNDEFINED_FILENAME);
2386   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2387   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2388
2389   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2390   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2391   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2392 #else
2393   artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
2394   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2395   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2396
2397   checked_free(artwork_new->name);
2398
2399   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
2400   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
2401   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2402 #endif
2403
2404   return artwork_new;
2405 }
2406
2407 void LoadArtworkInfo()
2408 {
2409   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2410
2411   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2412                                 options.graphics_directory,
2413                                 TREE_TYPE_GRAPHICS_DIR);
2414   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2415                                 getUserGraphicsDir(),
2416                                 TREE_TYPE_GRAPHICS_DIR);
2417
2418   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2419                                 options.sounds_directory,
2420                                 TREE_TYPE_SOUNDS_DIR);
2421   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2422                                 getUserSoundsDir(),
2423                                 TREE_TYPE_SOUNDS_DIR);
2424
2425   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2426                                 options.music_directory,
2427                                 TREE_TYPE_MUSIC_DIR);
2428   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2429                                 getUserMusicDir(),
2430                                 TREE_TYPE_MUSIC_DIR);
2431
2432   if (artwork.gfx_first == NULL)
2433     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2434   if (artwork.snd_first == NULL)
2435     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2436   if (artwork.mus_first == NULL)
2437     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2438
2439   /* before sorting, the first entries will be from the user directory */
2440   artwork.gfx_current =
2441     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2442   if (artwork.gfx_current == NULL)
2443     artwork.gfx_current =
2444       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2445   if (artwork.gfx_current == NULL)
2446     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2447
2448   artwork.snd_current =
2449     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2450   if (artwork.snd_current == NULL)
2451     artwork.snd_current =
2452       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2453   if (artwork.snd_current == NULL)
2454     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2455
2456   artwork.mus_current =
2457     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2458   if (artwork.mus_current == NULL)
2459     artwork.mus_current =
2460       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2461   if (artwork.mus_current == NULL)
2462     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2463
2464   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2465   artwork.snd_current_identifier = artwork.snd_current->identifier;
2466   artwork.mus_current_identifier = artwork.mus_current->identifier;
2467
2468 #if 0
2469   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2470   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2471   printf("music set == %s\n\n", artwork.mus_current_identifier);
2472 #endif
2473
2474   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2475   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2476   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2477
2478 #if 0
2479   dumpTreeInfo(artwork.gfx_first, 0);
2480   dumpTreeInfo(artwork.snd_first, 0);
2481   dumpTreeInfo(artwork.mus_first, 0);
2482 #endif
2483 }
2484
2485 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2486                                   LevelDirTree *level_node)
2487 {
2488   /* recursively check all level directories for artwork sub-directories */
2489
2490   while (level_node)
2491   {
2492     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2493                           ARTWORK_DIRECTORY((*artwork_node)->type));
2494
2495 #if 0
2496     if (!level_node->parent_link)
2497       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2498              level_node->filename, level_node->name);
2499 #endif
2500
2501     if (!level_node->parent_link)
2502     {
2503       TreeInfo *topnode_last = *artwork_node;
2504
2505       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2506                                     (*artwork_node)->type);
2507
2508       if (topnode_last != *artwork_node)
2509       {
2510         free((*artwork_node)->identifier);
2511         free((*artwork_node)->name);
2512         free((*artwork_node)->name_sorting);
2513
2514         (*artwork_node)->identifier   = getStringCopy(level_node->filename);
2515         (*artwork_node)->name         = getStringCopy(level_node->name);
2516         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2517
2518         (*artwork_node)->sort_priority = level_node->sort_priority;
2519         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2520       }
2521     }
2522
2523     free(path);
2524
2525     if (level_node->node_group != NULL)
2526       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2527
2528     level_node = level_node->next;
2529   }
2530 }
2531
2532 void LoadLevelArtworkInfo()
2533 {
2534   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2535
2536   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2537   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2538   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2539
2540   /* needed for reloading level artwork not known at ealier stage */
2541
2542   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2543   {
2544     artwork.gfx_current =
2545       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2546     if (artwork.gfx_current == NULL)
2547       artwork.gfx_current =
2548         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2549     if (artwork.gfx_current == NULL)
2550       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2551   }
2552
2553   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2554   {
2555     artwork.snd_current =
2556       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2557     if (artwork.snd_current == NULL)
2558       artwork.snd_current =
2559         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2560     if (artwork.snd_current == NULL)
2561       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2562   }
2563
2564   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2565   {
2566     artwork.mus_current =
2567       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2568     if (artwork.mus_current == NULL)
2569       artwork.mus_current =
2570         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2571     if (artwork.mus_current == NULL)
2572       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2573   }
2574
2575   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2576   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2577   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2578
2579 #if 0
2580   dumpTreeInfo(artwork.gfx_first, 0);
2581   dumpTreeInfo(artwork.snd_first, 0);
2582   dumpTreeInfo(artwork.mus_first, 0);
2583 #endif
2584 }
2585
2586 static void SaveUserLevelInfo()
2587 {
2588   LevelDirTree *level_info;
2589   char *filename;
2590   FILE *file;
2591   int i;
2592
2593   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2594
2595   if (!(file = fopen(filename, MODE_WRITE)))
2596   {
2597     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2598     free(filename);
2599     return;
2600   }
2601
2602   level_info = newTreeInfo();
2603
2604   /* always start with reliable default values */
2605   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2606
2607 #if 1
2608   setString(&level_info->name, getLoginName());
2609   setString(&level_info->author, getRealName());
2610   level_info->levels = 100;
2611   level_info->first_level = 1;
2612   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
2613   level_info->readonly = FALSE;
2614   setString(&level_info->graphics_set, GFX_CLASSIC_SUBDIR);
2615   setString(&level_info->sounds_set,   SND_CLASSIC_SUBDIR);
2616   setString(&level_info->music_set,    MUS_CLASSIC_SUBDIR);
2617 #else
2618   ldi.name = getStringCopy(getLoginName());
2619   ldi.author = getStringCopy(getRealName());
2620   ldi.levels = 100;
2621   ldi.first_level = 1;
2622   ldi.sort_priority = LEVELCLASS_PRIVATE_START;
2623   ldi.readonly = FALSE;
2624   ldi.graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
2625   ldi.sounds_set = getStringCopy(SND_CLASSIC_SUBDIR);
2626   ldi.music_set = getStringCopy(MUS_CLASSIC_SUBDIR);
2627 #endif
2628
2629   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2630                                                  getCookie("LEVELINFO")));
2631
2632   ldi = *level_info;
2633   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2634     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2635         i != LEVELINFO_TOKEN_NAME_SORTING &&
2636         i != LEVELINFO_TOKEN_IMPORTED_FROM)
2637       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2638
2639   fclose(file);
2640
2641   SetFilePermissions(filename, PERMS_PRIVATE);
2642
2643   freeTreeInfo(level_info);
2644   free(filename);
2645 }
2646
2647 char *getSetupValue(int type, void *value)
2648 {
2649   static char value_string[MAX_LINE_LEN];
2650
2651   if (value == NULL)
2652     return NULL;
2653
2654   switch (type)
2655   {
2656     case TYPE_BOOLEAN:
2657       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2658       break;
2659
2660     case TYPE_SWITCH:
2661       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2662       break;
2663
2664     case TYPE_YES_NO:
2665       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2666       break;
2667
2668     case TYPE_KEY:
2669       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2670       break;
2671
2672     case TYPE_KEY_X11:
2673       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2674       break;
2675
2676     case TYPE_INTEGER:
2677       sprintf(value_string, "%d", *(int *)value);
2678       break;
2679
2680     case TYPE_STRING:
2681       strcpy(value_string, *(char **)value);
2682       break;
2683
2684     default:
2685       value_string[0] = '\0';
2686       break;
2687   }
2688
2689   return value_string;
2690 }
2691
2692 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2693 {
2694   int i;
2695   char *line;
2696   static char token_string[MAX_LINE_LEN];
2697   int token_type = token_info[token_nr].type;
2698   void *setup_value = token_info[token_nr].value;
2699   char *token_text = token_info[token_nr].text;
2700   char *value_string = getSetupValue(token_type, setup_value);
2701
2702   /* build complete token string */
2703   sprintf(token_string, "%s%s", prefix, token_text);
2704
2705   /* build setup entry line */
2706   line = getFormattedSetupEntry(token_string, value_string);
2707
2708   if (token_type == TYPE_KEY_X11)
2709   {
2710     Key key = *(Key *)setup_value;
2711     char *keyname = getKeyNameFromKey(key);
2712
2713     /* add comment, if useful */
2714     if (strcmp(keyname, "(undefined)") != 0 &&
2715         strcmp(keyname, "(unknown)") != 0)
2716     {
2717       /* add at least one whitespace */
2718       strcat(line, " ");
2719       for (i = strlen(line); i < TOKEN_COMMENT_POSITION; i++)
2720         strcat(line, " ");
2721
2722       strcat(line, "# ");
2723       strcat(line, keyname);
2724     }
2725   }
2726
2727   return line;
2728 }
2729
2730 void LoadLevelSetup_LastSeries()
2731 {
2732   /* ----------------------------------------------------------------------- */
2733   /* ~/.<program>/levelsetup.conf                                            */
2734   /* ----------------------------------------------------------------------- */
2735
2736   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2737   SetupFileHash *level_setup_hash = NULL;
2738
2739   /* always start with reliable default values */
2740   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2741
2742   if ((level_setup_hash = loadSetupFileHash(filename)))
2743   {
2744     char *last_level_series =
2745       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2746
2747     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2748                                                  last_level_series);
2749     if (leveldir_current == NULL)
2750       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2751
2752     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2753
2754     freeSetupFileHash(level_setup_hash);
2755   }
2756   else
2757     Error(ERR_WARN, "using default setup values");
2758
2759   free(filename);
2760 }
2761
2762 void SaveLevelSetup_LastSeries()
2763 {
2764   /* ----------------------------------------------------------------------- */
2765   /* ~/.<program>/levelsetup.conf                                            */
2766   /* ----------------------------------------------------------------------- */
2767
2768   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2769   char *level_subdir = leveldir_current->filename;
2770   FILE *file;
2771
2772   InitUserDataDirectory();
2773
2774   if (!(file = fopen(filename, MODE_WRITE)))
2775   {
2776     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2777     free(filename);
2778     return;
2779   }
2780
2781   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2782                                                  getCookie("LEVELSETUP")));
2783   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2784                                                level_subdir));
2785
2786   fclose(file);
2787
2788   SetFilePermissions(filename, PERMS_PRIVATE);
2789
2790   free(filename);
2791 }
2792
2793 static void checkSeriesInfo()
2794 {
2795   static char *level_directory = NULL;
2796   DIR *dir;
2797   struct dirent *dir_entry;
2798
2799   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2800
2801   level_directory = getPath2((leveldir_current->user_defined ?
2802                               getUserLevelDir(NULL) :
2803                               options.level_directory),
2804                              leveldir_current->fullpath);
2805
2806   if ((dir = opendir(level_directory)) == NULL)
2807   {
2808     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2809     return;
2810   }
2811
2812   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2813   {
2814     if (strlen(dir_entry->d_name) > 4 &&
2815         dir_entry->d_name[3] == '.' &&
2816         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2817     {
2818       char levelnum_str[4];
2819       int levelnum_value;
2820
2821       strncpy(levelnum_str, dir_entry->d_name, 3);
2822       levelnum_str[3] = '\0';
2823
2824       levelnum_value = atoi(levelnum_str);
2825
2826 #if 0
2827       if (levelnum_value < leveldir_current->first_level)
2828       {
2829         Error(ERR_WARN, "additional level %d found", levelnum_value);
2830         leveldir_current->first_level = levelnum_value;
2831       }
2832       else if (levelnum_value > leveldir_current->last_level)
2833       {
2834         Error(ERR_WARN, "additional level %d found", levelnum_value);
2835         leveldir_current->last_level = levelnum_value;
2836       }
2837 #endif
2838     }
2839   }
2840
2841   closedir(dir);
2842 }
2843
2844 void LoadLevelSetup_SeriesInfo()
2845 {
2846   char *filename;
2847   SetupFileHash *level_setup_hash = NULL;
2848   char *level_subdir = leveldir_current->filename;
2849
2850   /* always start with reliable default values */
2851   level_nr = leveldir_current->first_level;
2852
2853   checkSeriesInfo(leveldir_current);
2854
2855   /* ----------------------------------------------------------------------- */
2856   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2857   /* ----------------------------------------------------------------------- */
2858
2859   level_subdir = leveldir_current->filename;
2860
2861   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2862
2863   if ((level_setup_hash = loadSetupFileHash(filename)))
2864   {
2865     char *token_value;
2866
2867     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2868
2869     if (token_value)
2870     {
2871       level_nr = atoi(token_value);
2872
2873       if (level_nr < leveldir_current->first_level)
2874         level_nr = leveldir_current->first_level;
2875       if (level_nr > leveldir_current->last_level)
2876         level_nr = leveldir_current->last_level;
2877     }
2878
2879     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2880
2881     if (token_value)
2882     {
2883       int level_nr = atoi(token_value);
2884
2885       if (level_nr < leveldir_current->first_level)
2886         level_nr = leveldir_current->first_level;
2887       if (level_nr > leveldir_current->last_level + 1)
2888         level_nr = leveldir_current->last_level;
2889
2890       if (leveldir_current->user_defined)
2891         level_nr = leveldir_current->last_level;
2892
2893       leveldir_current->handicap_level = level_nr;
2894     }
2895
2896     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2897
2898     freeSetupFileHash(level_setup_hash);
2899   }
2900   else
2901     Error(ERR_WARN, "using default setup values");
2902
2903   free(filename);
2904 }
2905
2906 void SaveLevelSetup_SeriesInfo()
2907 {
2908   char *filename;
2909   char *level_subdir = leveldir_current->filename;
2910   char *level_nr_str = int2str(level_nr, 0);
2911   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2912   FILE *file;
2913
2914   /* ----------------------------------------------------------------------- */
2915   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2916   /* ----------------------------------------------------------------------- */
2917
2918   InitLevelSetupDirectory(level_subdir);
2919
2920   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2921
2922   if (!(file = fopen(filename, MODE_WRITE)))
2923   {
2924     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2925     free(filename);
2926     return;
2927   }
2928
2929   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2930                                                  getCookie("LEVELSETUP")));
2931   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2932                                                level_nr_str));
2933   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2934                                                handicap_level_str));
2935
2936   fclose(file);
2937
2938   SetFilePermissions(filename, PERMS_PRIVATE);
2939
2940   free(filename);
2941 }