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