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