rnd-20060819-3-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   if (program.userdata_path == NULL)
1209     program.userdata_path = getPath2(getPersonalDataDir(),
1210                                      program.userdata_subdir);
1211
1212   return program.userdata_path;
1213 }
1214
1215 void updateUserGameDataDir()
1216 {
1217 #if defined(PLATFORM_MACOSX)
1218   char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1219   char *userdata_dir_new = getUserGameDataDir();
1220
1221   /* convert old Unix style game data directory to Mac OS X style, if needed */
1222   if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1223   {
1224     if (rename(userdata_dir_old, userdata_dir_new) != 0)
1225     {
1226       Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1227             userdata_dir_old, userdata_dir_new);
1228
1229       /* continue using Unix style data directory -- this should not happen */
1230       program.userdata_path = getPath2(getPersonalDataDir(),
1231                                        program.userdata_subdir_unix);
1232     }
1233   }
1234
1235   free(userdata_dir_old);
1236   free(userdata_dir_new);
1237 #endif
1238 }
1239
1240 char *getSetupDir()
1241 {
1242   return getUserGameDataDir();
1243 }
1244
1245 static mode_t posix_umask(mode_t mask)
1246 {
1247 #if defined(PLATFORM_UNIX)
1248   return umask(mask);
1249 #else
1250   return 0;
1251 #endif
1252 }
1253
1254 static int posix_mkdir(const char *pathname, mode_t mode)
1255 {
1256 #if defined(PLATFORM_WIN32)
1257   return mkdir(pathname);
1258 #else
1259   return mkdir(pathname, mode);
1260 #endif
1261 }
1262
1263 void createDirectory(char *dir, char *text, int permission_class)
1264 {
1265   /* leave "other" permissions in umask untouched, but ensure group parts
1266      of USERDATA_DIR_MODE are not masked */
1267   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1268                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1269   mode_t normal_umask = posix_umask(0);
1270   mode_t group_umask = ~(dir_mode & S_IRWXG);
1271   posix_umask(normal_umask & group_umask);
1272
1273   if (!fileExists(dir))
1274     if (posix_mkdir(dir, dir_mode) != 0)
1275       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1276
1277   posix_umask(normal_umask);            /* reset normal umask */
1278 }
1279
1280 void InitUserDataDirectory()
1281 {
1282   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1283 }
1284
1285 void SetFilePermissions(char *filename, int permission_class)
1286 {
1287   chmod(filename, (permission_class == PERMS_PRIVATE ?
1288                    FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1289 }
1290
1291 char *getCookie(char *file_type)
1292 {
1293   static char cookie[MAX_COOKIE_LEN + 1];
1294
1295   if (strlen(program.cookie_prefix) + 1 +
1296       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1297     return "[COOKIE ERROR]";    /* should never happen */
1298
1299   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1300           program.cookie_prefix, file_type,
1301           program.version_major, program.version_minor);
1302
1303   return cookie;
1304 }
1305
1306 int getFileVersionFromCookieString(const char *cookie)
1307 {
1308   const char *ptr_cookie1, *ptr_cookie2;
1309   const char *pattern1 = "_FILE_VERSION_";
1310   const char *pattern2 = "?.?";
1311   const int len_cookie = strlen(cookie);
1312   const int len_pattern1 = strlen(pattern1);
1313   const int len_pattern2 = strlen(pattern2);
1314   const int len_pattern = len_pattern1 + len_pattern2;
1315   int version_major, version_minor;
1316
1317   if (len_cookie <= len_pattern)
1318     return -1;
1319
1320   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1321   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1322
1323   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1324     return -1;
1325
1326   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1327       ptr_cookie2[1] != '.' ||
1328       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1329     return -1;
1330
1331   version_major = ptr_cookie2[0] - '0';
1332   version_minor = ptr_cookie2[2] - '0';
1333
1334   return VERSION_IDENT(version_major, version_minor, 0, 0);
1335 }
1336
1337 boolean checkCookieString(const char *cookie, const char *template)
1338 {
1339   const char *pattern = "_FILE_VERSION_?.?";
1340   const int len_cookie = strlen(cookie);
1341   const int len_template = strlen(template);
1342   const int len_pattern = strlen(pattern);
1343
1344   if (len_cookie != len_template)
1345     return FALSE;
1346
1347   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1348     return FALSE;
1349
1350   return TRUE;
1351 }
1352
1353 /* ------------------------------------------------------------------------- */
1354 /* setup file list and hash handling functions                               */
1355 /* ------------------------------------------------------------------------- */
1356
1357 char *getFormattedSetupEntry(char *token, char *value)
1358 {
1359   int i;
1360   static char entry[MAX_LINE_LEN];
1361
1362   /* if value is an empty string, just return token without value */
1363   if (*value == '\0')
1364     return token;
1365
1366   /* start with the token and some spaces to format output line */
1367   sprintf(entry, "%s:", token);
1368   for (i = strlen(entry); i < token_value_position; i++)
1369     strcat(entry, " ");
1370
1371   /* continue with the token's value */
1372   strcat(entry, value);
1373
1374   return entry;
1375 }
1376
1377 SetupFileList *newSetupFileList(char *token, char *value)
1378 {
1379   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1380
1381   new->token = getStringCopy(token);
1382   new->value = getStringCopy(value);
1383
1384   new->next = NULL;
1385
1386   return new;
1387 }
1388
1389 void freeSetupFileList(SetupFileList *list)
1390 {
1391   if (list == NULL)
1392     return;
1393
1394   checked_free(list->token);
1395   checked_free(list->value);
1396
1397   if (list->next)
1398     freeSetupFileList(list->next);
1399
1400   free(list);
1401 }
1402
1403 char *getListEntry(SetupFileList *list, char *token)
1404 {
1405   if (list == NULL)
1406     return NULL;
1407
1408   if (strEqual(list->token, token))
1409     return list->value;
1410   else
1411     return getListEntry(list->next, token);
1412 }
1413
1414 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1415 {
1416   if (list == NULL)
1417     return NULL;
1418
1419   if (strEqual(list->token, token))
1420   {
1421     checked_free(list->value);
1422
1423     list->value = getStringCopy(value);
1424
1425     return list;
1426   }
1427   else if (list->next == NULL)
1428     return (list->next = newSetupFileList(token, value));
1429   else
1430     return setListEntry(list->next, token, value);
1431 }
1432
1433 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1434 {
1435   if (list == NULL)
1436     return NULL;
1437
1438   if (list->next == NULL)
1439     return (list->next = newSetupFileList(token, value));
1440   else
1441     return addListEntry(list->next, token, value);
1442 }
1443
1444 #ifdef DEBUG
1445 static void printSetupFileList(SetupFileList *list)
1446 {
1447   if (!list)
1448     return;
1449
1450   printf("token: '%s'\n", list->token);
1451   printf("value: '%s'\n", list->value);
1452
1453   printSetupFileList(list->next);
1454 }
1455 #endif
1456
1457 #ifdef DEBUG
1458 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1459 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1460 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1461 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1462 #else
1463 #define insert_hash_entry hashtable_insert
1464 #define search_hash_entry hashtable_search
1465 #define change_hash_entry hashtable_change
1466 #define remove_hash_entry hashtable_remove
1467 #endif
1468
1469 static unsigned int get_hash_from_key(void *key)
1470 {
1471   /*
1472     djb2
1473
1474     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1475     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1476     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1477     it works better than many other constants, prime or not) has never been
1478     adequately explained.
1479
1480     If you just want to have a good hash function, and cannot wait, djb2
1481     is one of the best string hash functions i know. It has excellent
1482     distribution and speed on many different sets of keys and table sizes.
1483     You are not likely to do better with one of the "well known" functions
1484     such as PJW, K&R, etc.
1485
1486     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1487   */
1488
1489   char *str = (char *)key;
1490   unsigned int hash = 5381;
1491   int c;
1492
1493   while ((c = *str++))
1494     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1495
1496   return hash;
1497 }
1498
1499 static int keys_are_equal(void *key1, void *key2)
1500 {
1501   return (strEqual((char *)key1, (char *)key2));
1502 }
1503
1504 SetupFileHash *newSetupFileHash()
1505 {
1506   SetupFileHash *new_hash =
1507     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1508
1509   if (new_hash == NULL)
1510     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1511
1512   return new_hash;
1513 }
1514
1515 void freeSetupFileHash(SetupFileHash *hash)
1516 {
1517   if (hash == NULL)
1518     return;
1519
1520   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1521 }
1522
1523 char *getHashEntry(SetupFileHash *hash, char *token)
1524 {
1525   if (hash == NULL)
1526     return NULL;
1527
1528   return search_hash_entry(hash, token);
1529 }
1530
1531 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1532 {
1533   char *value_copy;
1534
1535   if (hash == NULL)
1536     return;
1537
1538   value_copy = getStringCopy(value);
1539
1540   /* change value; if it does not exist, insert it as new */
1541   if (!change_hash_entry(hash, token, value_copy))
1542     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1543       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1544 }
1545
1546 char *removeHashEntry(SetupFileHash *hash, char *token)
1547 {
1548   if (hash == NULL)
1549     return NULL;
1550
1551   return remove_hash_entry(hash, token);
1552 }
1553
1554 #if 0
1555 static void printSetupFileHash(SetupFileHash *hash)
1556 {
1557   BEGIN_HASH_ITERATION(hash, itr)
1558   {
1559     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1560     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1561   }
1562   END_HASH_ITERATION(hash, itr)
1563 }
1564 #endif
1565
1566 static void *loadSetupFileData(char *filename, boolean use_hash)
1567 {
1568   char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1569   char *token, *value, *line_ptr;
1570   void *setup_file_data, *insert_ptr = NULL;
1571   boolean read_continued_line = FALSE;
1572   FILE *file;
1573
1574   if (!(file = fopen(filename, MODE_READ)))
1575   {
1576     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1577
1578     return NULL;
1579   }
1580
1581   if (use_hash)
1582     setup_file_data = newSetupFileHash();
1583   else
1584     insert_ptr = setup_file_data = newSetupFileList("", "");
1585
1586   while (!feof(file))
1587   {
1588     /* read next line of input file */
1589     if (!fgets(line, MAX_LINE_LEN, file))
1590       break;
1591
1592     /* cut trailing newline or carriage return */
1593     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1594       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1595         *line_ptr = '\0';
1596
1597     if (read_continued_line)
1598     {
1599       /* cut leading whitespaces from input line */
1600       for (line_ptr = line; *line_ptr; line_ptr++)
1601         if (*line_ptr != ' ' && *line_ptr != '\t')
1602           break;
1603
1604       /* append new line to existing line, if there is enough space */
1605       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1606         strcat(previous_line, line_ptr);
1607
1608       strcpy(line, previous_line);      /* copy storage buffer to line */
1609
1610       read_continued_line = FALSE;
1611     }
1612
1613     /* if the last character is '\', continue at next line */
1614     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1615     {
1616       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
1617       strcpy(previous_line, line);      /* copy line to storage buffer */
1618
1619       read_continued_line = TRUE;
1620
1621       continue;
1622     }
1623
1624     /* cut trailing comment from input line */
1625     for (line_ptr = line; *line_ptr; line_ptr++)
1626     {
1627       if (*line_ptr == '#')
1628       {
1629         *line_ptr = '\0';
1630         break;
1631       }
1632     }
1633
1634     /* cut trailing whitespaces from input line */
1635     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1636       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1637         *line_ptr = '\0';
1638
1639     /* ignore empty lines */
1640     if (*line == '\0')
1641       continue;
1642
1643     /* cut leading whitespaces from token */
1644     for (token = line; *token; token++)
1645       if (*token != ' ' && *token != '\t')
1646         break;
1647
1648     /* start with empty value as reliable default */
1649     value = "";
1650
1651     /* find end of token to determine start of value */
1652     for (line_ptr = token; *line_ptr; line_ptr++)
1653     {
1654       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1655       {
1656         *line_ptr = '\0';               /* terminate token string */
1657         value = line_ptr + 1;           /* set beginning of value */
1658
1659         break;
1660       }
1661     }
1662
1663     /* cut leading whitespaces from value */
1664     for (; *value; value++)
1665       if (*value != ' ' && *value != '\t')
1666         break;
1667
1668 #if 0
1669     if (*value == '\0')
1670       value = "true";   /* treat tokens without value as "true" */
1671 #endif
1672
1673     if (*token)
1674     {
1675       if (use_hash)
1676         setHashEntry((SetupFileHash *)setup_file_data, token, value);
1677       else
1678         insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1679     }
1680   }
1681
1682   fclose(file);
1683
1684   if (use_hash)
1685   {
1686     if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1687       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1688   }
1689   else
1690   {
1691     SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1692     SetupFileList *first_valid_list_entry = setup_file_list->next;
1693
1694     /* free empty list header */
1695     setup_file_list->next = NULL;
1696     freeSetupFileList(setup_file_list);
1697     setup_file_data = first_valid_list_entry;
1698
1699     if (first_valid_list_entry == NULL)
1700       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1701   }
1702
1703   return setup_file_data;
1704 }
1705
1706 SetupFileList *loadSetupFileList(char *filename)
1707 {
1708   return (SetupFileList *)loadSetupFileData(filename, FALSE);
1709 }
1710
1711 SetupFileHash *loadSetupFileHash(char *filename)
1712 {
1713   return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1714 }
1715
1716 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1717                                   char *filename, char *identifier)
1718 {
1719   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1720
1721   if (value == NULL)
1722     Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1723   else if (!checkCookieString(value, identifier))
1724     Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1725 }
1726
1727
1728 /* ========================================================================= */
1729 /* setup file stuff                                                          */
1730 /* ========================================================================= */
1731
1732 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
1733 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
1734 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
1735
1736 /* level directory info */
1737 #define LEVELINFO_TOKEN_IDENTIFIER              0
1738 #define LEVELINFO_TOKEN_NAME                    1
1739 #define LEVELINFO_TOKEN_NAME_SORTING            2
1740 #define LEVELINFO_TOKEN_AUTHOR                  3
1741 #define LEVELINFO_TOKEN_IMPORTED_FROM           4
1742 #define LEVELINFO_TOKEN_IMPORTED_BY             5
1743 #define LEVELINFO_TOKEN_LEVELS                  6
1744 #define LEVELINFO_TOKEN_FIRST_LEVEL             7
1745 #define LEVELINFO_TOKEN_SORT_PRIORITY           8
1746 #define LEVELINFO_TOKEN_LATEST_ENGINE           9
1747 #define LEVELINFO_TOKEN_LEVEL_GROUP             10
1748 #define LEVELINFO_TOKEN_READONLY                11
1749 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        12
1750 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        13
1751 #define LEVELINFO_TOKEN_GRAPHICS_SET            14
1752 #define LEVELINFO_TOKEN_SOUNDS_SET              15
1753 #define LEVELINFO_TOKEN_MUSIC_SET               16
1754 #define LEVELINFO_TOKEN_FILENAME                17
1755 #define LEVELINFO_TOKEN_FILETYPE                18
1756 #define LEVELINFO_TOKEN_HANDICAP                19
1757 #define LEVELINFO_TOKEN_SKIP_LEVELS             20
1758
1759 #define NUM_LEVELINFO_TOKENS                    21
1760
1761 static LevelDirTree ldi;
1762
1763 static struct TokenInfo levelinfo_tokens[] =
1764 {
1765   /* level directory info */
1766   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
1767   { TYPE_STRING,        &ldi.name,              "name"                  },
1768   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
1769   { TYPE_STRING,        &ldi.author,            "author"                },
1770   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
1771   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
1772   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
1773   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
1774   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
1775   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
1776   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
1777   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
1778   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
1779   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
1780   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
1781   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
1782   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
1783   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
1784   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
1785   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
1786   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           }
1787 };
1788
1789 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1790 {
1791   ti->type = type;
1792
1793   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
1794                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1795                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
1796                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
1797                   NULL);
1798
1799   ti->node_parent = NULL;
1800   ti->node_group = NULL;
1801   ti->next = NULL;
1802
1803   ti->cl_first = -1;
1804   ti->cl_cursor = -1;
1805
1806   ti->subdir = NULL;
1807   ti->fullpath = NULL;
1808   ti->basepath = NULL;
1809   ti->identifier = NULL;
1810   ti->name = getStringCopy(ANONYMOUS_NAME);
1811   ti->name_sorting = NULL;
1812   ti->author = getStringCopy(ANONYMOUS_NAME);
1813
1814   ti->sort_priority = LEVELCLASS_UNDEFINED;     /* default: least priority */
1815   ti->latest_engine = FALSE;                    /* default: get from level */
1816   ti->parent_link = FALSE;
1817   ti->in_user_dir = FALSE;
1818   ti->user_defined = FALSE;
1819   ti->color = 0;
1820   ti->class_desc = NULL;
1821
1822   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
1823
1824   if (ti->type == TREE_TYPE_LEVEL_DIR)
1825   {
1826     ti->imported_from = NULL;
1827     ti->imported_by = NULL;
1828
1829     ti->graphics_set_ecs = NULL;
1830     ti->graphics_set_aga = NULL;
1831     ti->graphics_set = NULL;
1832     ti->sounds_set = NULL;
1833     ti->music_set = NULL;
1834     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1835     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1836     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1837
1838     ti->level_filename = NULL;
1839     ti->level_filetype = NULL;
1840
1841     ti->levels = 0;
1842     ti->first_level = 0;
1843     ti->last_level = 0;
1844     ti->level_group = FALSE;
1845     ti->handicap_level = 0;
1846     ti->readonly = TRUE;
1847     ti->handicap = TRUE;
1848     ti->skip_levels = FALSE;
1849   }
1850 }
1851
1852 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
1853 {
1854   if (parent == NULL)
1855   {
1856     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1857
1858     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
1859
1860     return;
1861   }
1862
1863   /* copy all values from the parent structure */
1864
1865   ti->type = parent->type;
1866
1867   ti->node_top = parent->node_top;
1868   ti->node_parent = parent;
1869   ti->node_group = NULL;
1870   ti->next = NULL;
1871
1872   ti->cl_first = -1;
1873   ti->cl_cursor = -1;
1874
1875   ti->subdir = NULL;
1876   ti->fullpath = NULL;
1877   ti->basepath = NULL;
1878   ti->identifier = NULL;
1879   ti->name = getStringCopy(ANONYMOUS_NAME);
1880   ti->name_sorting = NULL;
1881   ti->author = getStringCopy(parent->author);
1882
1883   ti->sort_priority = parent->sort_priority;
1884   ti->latest_engine = parent->latest_engine;
1885   ti->parent_link = FALSE;
1886   ti->in_user_dir = parent->in_user_dir;
1887   ti->user_defined = parent->user_defined;
1888   ti->color = parent->color;
1889   ti->class_desc = getStringCopy(parent->class_desc);
1890
1891   ti->infotext = getStringCopy(parent->infotext);
1892
1893   if (ti->type == TREE_TYPE_LEVEL_DIR)
1894   {
1895     ti->imported_from = getStringCopy(parent->imported_from);
1896     ti->imported_by = getStringCopy(parent->imported_by);
1897
1898     ti->graphics_set_ecs = NULL;
1899     ti->graphics_set_aga = NULL;
1900     ti->graphics_set = NULL;
1901     ti->sounds_set = NULL;
1902     ti->music_set = NULL;
1903     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1904     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1905     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
1906
1907     ti->level_filename = NULL;
1908     ti->level_filetype = NULL;
1909
1910     ti->levels = 0;
1911     ti->first_level = 0;
1912     ti->last_level = 0;
1913     ti->level_group = FALSE;
1914     ti->handicap_level = 0;
1915     ti->readonly = TRUE;
1916     ti->handicap = TRUE;
1917     ti->skip_levels = FALSE;
1918   }
1919 }
1920
1921 static void freeTreeInfo(TreeInfo *ti)
1922 {
1923   checked_free(ti->subdir);
1924   checked_free(ti->fullpath);
1925   checked_free(ti->basepath);
1926   checked_free(ti->identifier);
1927
1928   checked_free(ti->name);
1929   checked_free(ti->name_sorting);
1930   checked_free(ti->author);
1931
1932   checked_free(ti->class_desc);
1933
1934   checked_free(ti->infotext);
1935
1936   if (ti->type == TREE_TYPE_LEVEL_DIR)
1937   {
1938     checked_free(ti->imported_from);
1939     checked_free(ti->imported_by);
1940
1941     checked_free(ti->graphics_set_ecs);
1942     checked_free(ti->graphics_set_aga);
1943     checked_free(ti->graphics_set);
1944     checked_free(ti->sounds_set);
1945     checked_free(ti->music_set);
1946
1947     checked_free(ti->graphics_path);
1948     checked_free(ti->sounds_path);
1949     checked_free(ti->music_path);
1950
1951     checked_free(ti->level_filename);
1952     checked_free(ti->level_filetype);
1953   }
1954 }
1955
1956 void setSetupInfo(struct TokenInfo *token_info,
1957                   int token_nr, char *token_value)
1958 {
1959   int token_type = token_info[token_nr].type;
1960   void *setup_value = token_info[token_nr].value;
1961
1962   if (token_value == NULL)
1963     return;
1964
1965   /* set setup field to corresponding token value */
1966   switch (token_type)
1967   {
1968     case TYPE_BOOLEAN:
1969     case TYPE_SWITCH:
1970       *(boolean *)setup_value = get_boolean_from_string(token_value);
1971       break;
1972
1973     case TYPE_KEY:
1974       *(Key *)setup_value = getKeyFromKeyName(token_value);
1975       break;
1976
1977     case TYPE_KEY_X11:
1978       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1979       break;
1980
1981     case TYPE_INTEGER:
1982       *(int *)setup_value = get_integer_from_string(token_value);
1983       break;
1984
1985     case TYPE_STRING:
1986       checked_free(*(char **)setup_value);
1987       *(char **)setup_value = getStringCopy(token_value);
1988       break;
1989
1990     default:
1991       break;
1992   }
1993 }
1994
1995 static int compareTreeInfoEntries(const void *object1, const void *object2)
1996 {
1997   const TreeInfo *entry1 = *((TreeInfo **)object1);
1998   const TreeInfo *entry2 = *((TreeInfo **)object2);
1999   int class_sorting1, class_sorting2;
2000   int compare_result;
2001
2002   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2003   {
2004     class_sorting1 = LEVELSORTING(entry1);
2005     class_sorting2 = LEVELSORTING(entry2);
2006   }
2007   else
2008   {
2009     class_sorting1 = ARTWORKSORTING(entry1);
2010     class_sorting2 = ARTWORKSORTING(entry2);
2011   }
2012
2013   if (entry1->parent_link || entry2->parent_link)
2014     compare_result = (entry1->parent_link ? -1 : +1);
2015   else if (entry1->sort_priority == entry2->sort_priority)
2016   {
2017     char *name1 = getStringToLower(entry1->name_sorting);
2018     char *name2 = getStringToLower(entry2->name_sorting);
2019
2020     compare_result = strcmp(name1, name2);
2021
2022     free(name1);
2023     free(name2);
2024   }
2025   else if (class_sorting1 == class_sorting2)
2026     compare_result = entry1->sort_priority - entry2->sort_priority;
2027   else
2028     compare_result = class_sorting1 - class_sorting2;
2029
2030   return compare_result;
2031 }
2032
2033 static void createParentTreeInfoNode(TreeInfo *node_parent)
2034 {
2035   TreeInfo *ti_new;
2036
2037   if (node_parent == NULL)
2038     return;
2039
2040   ti_new = newTreeInfo();
2041   setTreeInfoToDefaults(ti_new, node_parent->type);
2042
2043   ti_new->node_parent = node_parent;
2044   ti_new->parent_link = TRUE;
2045
2046   setString(&ti_new->identifier, node_parent->identifier);
2047   setString(&ti_new->name, ".. (parent directory)");
2048   setString(&ti_new->name_sorting, ti_new->name);
2049
2050   setString(&ti_new->subdir, "..");
2051   setString(&ti_new->fullpath, node_parent->fullpath);
2052
2053   ti_new->sort_priority = node_parent->sort_priority;
2054   ti_new->latest_engine = node_parent->latest_engine;
2055
2056   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2057
2058   pushTreeInfo(&node_parent->node_group, ti_new);
2059 }
2060
2061 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2062 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2063
2064 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2065                                           TreeInfo *node_parent,
2066                                           char *level_directory,
2067                                           char *directory_name)
2068 {
2069   char *directory_path = getPath2(level_directory, directory_name);
2070   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2071   SetupFileHash *setup_file_hash;
2072   LevelDirTree *leveldir_new = NULL;
2073   int i;
2074
2075   /* unless debugging, silently ignore directories without "levelinfo.conf" */
2076   if (!options.debug && !fileExists(filename))
2077   {
2078     free(directory_path);
2079     free(filename);
2080
2081     return FALSE;
2082   }
2083
2084   setup_file_hash = loadSetupFileHash(filename);
2085
2086   if (setup_file_hash == NULL)
2087   {
2088     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2089
2090     free(directory_path);
2091     free(filename);
2092
2093     return FALSE;
2094   }
2095
2096   leveldir_new = newTreeInfo();
2097
2098   if (node_parent)
2099     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2100   else
2101     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2102
2103   leveldir_new->subdir = getStringCopy(directory_name);
2104
2105   checkSetupFileHashIdentifier(setup_file_hash, filename,
2106                                getCookie("LEVELINFO"));
2107
2108   /* set all structure fields according to the token/value pairs */
2109   ldi = *leveldir_new;
2110   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2111     setSetupInfo(levelinfo_tokens, i,
2112                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2113   *leveldir_new = ldi;
2114
2115   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2116     setString(&leveldir_new->name, leveldir_new->subdir);
2117
2118   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2119
2120   if (leveldir_new->identifier == NULL)
2121     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2122
2123   if (leveldir_new->name_sorting == NULL)
2124     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2125
2126   if (node_parent == NULL)              /* top level group */
2127   {
2128     leveldir_new->basepath = getStringCopy(level_directory);
2129     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2130   }
2131   else                                  /* sub level group */
2132   {
2133     leveldir_new->basepath = getStringCopy(node_parent->basepath);
2134     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2135   }
2136
2137 #if 0
2138   if (leveldir_new->levels < 1)
2139     leveldir_new->levels = 1;
2140 #endif
2141
2142   leveldir_new->last_level =
2143     leveldir_new->first_level + leveldir_new->levels - 1;
2144
2145   leveldir_new->in_user_dir =
2146     (!strEqual(leveldir_new->basepath, options.level_directory));
2147
2148   /* adjust some settings if user's private level directory was detected */
2149   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2150       leveldir_new->in_user_dir &&
2151       (strEqual(leveldir_new->subdir, getLoginName()) ||
2152        strEqual(leveldir_new->name,   getLoginName()) ||
2153        strEqual(leveldir_new->author, getRealName())))
2154   {
2155     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2156     leveldir_new->readonly = FALSE;
2157   }
2158
2159   leveldir_new->user_defined =
2160     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2161
2162   leveldir_new->color = LEVELCOLOR(leveldir_new);
2163
2164   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2165
2166   leveldir_new->handicap_level =        /* set handicap to default value */
2167     (leveldir_new->user_defined || !leveldir_new->handicap ?
2168      leveldir_new->last_level : leveldir_new->first_level);
2169
2170 #if 0
2171   /* !!! don't skip sets without levels (else artwork base sets are missing) */
2172 #if 1
2173   if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2174   {
2175     /* skip level sets without levels (which are probably artwork base sets) */
2176
2177     freeSetupFileHash(setup_file_hash);
2178     free(directory_path);
2179     free(filename);
2180
2181     return FALSE;
2182   }
2183 #endif
2184 #endif
2185
2186   pushTreeInfo(node_first, leveldir_new);
2187
2188   freeSetupFileHash(setup_file_hash);
2189
2190   if (leveldir_new->level_group)
2191   {
2192     /* create node to link back to current level directory */
2193     createParentTreeInfoNode(leveldir_new);
2194
2195     /* step into sub-directory and look for more level series */
2196     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2197                               leveldir_new, directory_path);
2198   }
2199
2200   free(directory_path);
2201   free(filename);
2202
2203   return TRUE;
2204 }
2205
2206 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2207                                       TreeInfo *node_parent,
2208                                       char *level_directory)
2209 {
2210   DIR *dir;
2211   struct dirent *dir_entry;
2212   boolean valid_entry_found = FALSE;
2213
2214   if ((dir = opendir(level_directory)) == NULL)
2215   {
2216     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2217     return;
2218   }
2219
2220   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2221   {
2222     struct stat file_status;
2223     char *directory_name = dir_entry->d_name;
2224     char *directory_path = getPath2(level_directory, directory_name);
2225
2226     /* skip entries for current and parent directory */
2227     if (strEqual(directory_name, ".") ||
2228         strEqual(directory_name, ".."))
2229     {
2230       free(directory_path);
2231       continue;
2232     }
2233
2234     /* find out if directory entry is itself a directory */
2235     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2236         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2237     {
2238       free(directory_path);
2239       continue;
2240     }
2241
2242     free(directory_path);
2243
2244     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2245         strEqual(directory_name, SOUNDS_DIRECTORY) ||
2246         strEqual(directory_name, MUSIC_DIRECTORY))
2247       continue;
2248
2249     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2250                                                     level_directory,
2251                                                     directory_name);
2252   }
2253
2254   closedir(dir);
2255
2256   /* special case: top level directory may directly contain "levelinfo.conf" */
2257   if (node_parent == NULL && !valid_entry_found)
2258   {
2259     /* check if this directory directly contains a file "levelinfo.conf" */
2260     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2261                                                     level_directory, ".");
2262   }
2263
2264   if (!valid_entry_found)
2265     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2266           level_directory);
2267 }
2268
2269 boolean AdjustGraphicsForEMC()
2270 {
2271   boolean settings_changed = FALSE;
2272
2273   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2274   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2275
2276   return settings_changed;
2277 }
2278
2279 void LoadLevelInfo()
2280 {
2281   InitUserLevelDirectory(getLoginName());
2282
2283   DrawInitText("Loading level series:", 120, FC_GREEN);
2284
2285   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2286   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2287
2288   /* after loading all level set information, clone the level directory tree
2289      and remove all level sets without levels (these may still contain artwork
2290      to be offered in the setup menu as "custom artwork", and are therefore
2291      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2292   leveldir_first_all = leveldir_first;
2293   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2294
2295   AdjustGraphicsForEMC();
2296
2297   /* before sorting, the first entries will be from the user directory */
2298   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2299
2300   if (leveldir_first == NULL)
2301     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2302
2303   sortTreeInfo(&leveldir_first);
2304
2305 #if 0
2306   dumpTreeInfo(leveldir_first, 0);
2307 #endif
2308 }
2309
2310 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2311                                               TreeInfo *node_parent,
2312                                               char *base_directory,
2313                                               char *directory_name, int type)
2314 {
2315   char *directory_path = getPath2(base_directory, directory_name);
2316   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2317   SetupFileHash *setup_file_hash = NULL;
2318   TreeInfo *artwork_new = NULL;
2319   int i;
2320
2321   if (fileExists(filename))
2322     setup_file_hash = loadSetupFileHash(filename);
2323
2324   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2325   {
2326     DIR *dir;
2327     struct dirent *dir_entry;
2328     boolean valid_file_found = FALSE;
2329
2330     if ((dir = opendir(directory_path)) != NULL)
2331     {
2332       while ((dir_entry = readdir(dir)) != NULL)
2333       {
2334         char *entry_name = dir_entry->d_name;
2335
2336         if (FileIsArtworkType(entry_name, type))
2337         {
2338           valid_file_found = TRUE;
2339           break;
2340         }
2341       }
2342
2343       closedir(dir);
2344     }
2345
2346     if (!valid_file_found)
2347     {
2348       if (!strEqual(directory_name, "."))
2349         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2350
2351       free(directory_path);
2352       free(filename);
2353
2354       return FALSE;
2355     }
2356   }
2357
2358   artwork_new = newTreeInfo();
2359
2360   if (node_parent)
2361     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2362   else
2363     setTreeInfoToDefaults(artwork_new, type);
2364
2365   artwork_new->subdir = getStringCopy(directory_name);
2366
2367   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2368   {
2369 #if 0
2370     checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2371 #endif
2372
2373     /* set all structure fields according to the token/value pairs */
2374     ldi = *artwork_new;
2375     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2376       setSetupInfo(levelinfo_tokens, i,
2377                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2378     *artwork_new = ldi;
2379
2380     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2381       setString(&artwork_new->name, artwork_new->subdir);
2382
2383 #if 0
2384     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2385 #endif
2386
2387     if (artwork_new->identifier == NULL)
2388       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2389
2390     if (artwork_new->name_sorting == NULL)
2391       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2392   }
2393
2394   if (node_parent == NULL)              /* top level group */
2395   {
2396     artwork_new->basepath = getStringCopy(base_directory);
2397     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2398   }
2399   else                                  /* sub level group */
2400   {
2401     artwork_new->basepath = getStringCopy(node_parent->basepath);
2402     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2403   }
2404
2405   artwork_new->in_user_dir =
2406     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2407
2408   /* (may use ".sort_priority" from "setup_file_hash" above) */
2409   artwork_new->color = ARTWORKCOLOR(artwork_new);
2410
2411   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2412
2413   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2414   {
2415     if (strEqual(artwork_new->subdir, "."))
2416     {
2417       if (artwork_new->user_defined)
2418       {
2419         setString(&artwork_new->identifier, "private");
2420         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2421       }
2422       else
2423       {
2424         setString(&artwork_new->identifier, "classic");
2425         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2426       }
2427
2428       /* set to new values after changing ".sort_priority" */
2429       artwork_new->color = ARTWORKCOLOR(artwork_new);
2430
2431       setString(&artwork_new->class_desc,
2432                 getLevelClassDescription(artwork_new));
2433     }
2434     else
2435     {
2436       setString(&artwork_new->identifier, artwork_new->subdir);
2437     }
2438
2439     setString(&artwork_new->name, artwork_new->identifier);
2440     setString(&artwork_new->name_sorting, artwork_new->name);
2441   }
2442
2443   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2444
2445   pushTreeInfo(node_first, artwork_new);
2446
2447   freeSetupFileHash(setup_file_hash);
2448
2449   free(directory_path);
2450   free(filename);
2451
2452   return TRUE;
2453 }
2454
2455 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2456                                           TreeInfo *node_parent,
2457                                           char *base_directory, int type)
2458 {
2459   DIR *dir;
2460   struct dirent *dir_entry;
2461   boolean valid_entry_found = FALSE;
2462
2463   if ((dir = opendir(base_directory)) == NULL)
2464   {
2465     /* display error if directory is main "options.graphics_directory" etc. */
2466     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2467       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2468
2469     return;
2470   }
2471
2472   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2473   {
2474     struct stat file_status;
2475     char *directory_name = dir_entry->d_name;
2476     char *directory_path = getPath2(base_directory, directory_name);
2477
2478     /* skip directory entries for current and parent directory */
2479     if (strEqual(directory_name, ".") ||
2480         strEqual(directory_name, ".."))
2481     {
2482       free(directory_path);
2483       continue;
2484     }
2485
2486     /* skip directory entries which are not a directory or are not accessible */
2487     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2488         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2489     {
2490       free(directory_path);
2491       continue;
2492     }
2493
2494     free(directory_path);
2495
2496     /* check if this directory contains artwork with or without config file */
2497     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2498                                                         base_directory,
2499                                                         directory_name, type);
2500   }
2501
2502   closedir(dir);
2503
2504   /* check if this directory directly contains artwork itself */
2505   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2506                                                       base_directory, ".",
2507                                                       type);
2508   if (!valid_entry_found)
2509     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2510           base_directory);
2511 }
2512
2513 static TreeInfo *getDummyArtworkInfo(int type)
2514 {
2515   /* this is only needed when there is completely no artwork available */
2516   TreeInfo *artwork_new = newTreeInfo();
2517
2518   setTreeInfoToDefaults(artwork_new, type);
2519
2520   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
2521   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2522   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2523
2524   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2525   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2526   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2527
2528   return artwork_new;
2529 }
2530
2531 void LoadArtworkInfo()
2532 {
2533   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2534
2535   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2536                                 options.graphics_directory,
2537                                 TREE_TYPE_GRAPHICS_DIR);
2538   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2539                                 getUserGraphicsDir(),
2540                                 TREE_TYPE_GRAPHICS_DIR);
2541
2542   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2543                                 options.sounds_directory,
2544                                 TREE_TYPE_SOUNDS_DIR);
2545   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2546                                 getUserSoundsDir(),
2547                                 TREE_TYPE_SOUNDS_DIR);
2548
2549   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2550                                 options.music_directory,
2551                                 TREE_TYPE_MUSIC_DIR);
2552   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2553                                 getUserMusicDir(),
2554                                 TREE_TYPE_MUSIC_DIR);
2555
2556   if (artwork.gfx_first == NULL)
2557     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2558   if (artwork.snd_first == NULL)
2559     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2560   if (artwork.mus_first == NULL)
2561     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2562
2563   /* before sorting, the first entries will be from the user directory */
2564   artwork.gfx_current =
2565     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2566   if (artwork.gfx_current == NULL)
2567     artwork.gfx_current =
2568       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2569   if (artwork.gfx_current == NULL)
2570     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2571
2572   artwork.snd_current =
2573     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2574   if (artwork.snd_current == NULL)
2575     artwork.snd_current =
2576       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2577   if (artwork.snd_current == NULL)
2578     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2579
2580   artwork.mus_current =
2581     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2582   if (artwork.mus_current == NULL)
2583     artwork.mus_current =
2584       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2585   if (artwork.mus_current == NULL)
2586     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2587
2588   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2589   artwork.snd_current_identifier = artwork.snd_current->identifier;
2590   artwork.mus_current_identifier = artwork.mus_current->identifier;
2591
2592 #if 0
2593   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2594   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2595   printf("music set == %s\n\n", artwork.mus_current_identifier);
2596 #endif
2597
2598   sortTreeInfo(&artwork.gfx_first);
2599   sortTreeInfo(&artwork.snd_first);
2600   sortTreeInfo(&artwork.mus_first);
2601
2602 #if 0
2603   dumpTreeInfo(artwork.gfx_first, 0);
2604   dumpTreeInfo(artwork.snd_first, 0);
2605   dumpTreeInfo(artwork.mus_first, 0);
2606 #endif
2607 }
2608
2609 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2610                                   LevelDirTree *level_node)
2611 {
2612   /* recursively check all level directories for artwork sub-directories */
2613
2614   while (level_node)
2615   {
2616     /* check all tree entries for artwork, but skip parent link entries */
2617     if (!level_node->parent_link)
2618     {
2619       TreeInfo *topnode_last = *artwork_node;
2620       char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2621                             ARTWORK_DIRECTORY((*artwork_node)->type));
2622
2623       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2624                                     (*artwork_node)->type);
2625
2626       if (topnode_last != *artwork_node)
2627       {
2628         free((*artwork_node)->identifier);
2629         free((*artwork_node)->name);
2630         free((*artwork_node)->name_sorting);
2631
2632         (*artwork_node)->identifier   = getStringCopy(level_node->subdir);
2633         (*artwork_node)->name         = getStringCopy(level_node->name);
2634         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2635
2636         (*artwork_node)->sort_priority = level_node->sort_priority;
2637         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2638       }
2639
2640       free(path);
2641     }
2642
2643     if (level_node->node_group != NULL)
2644       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2645
2646     level_node = level_node->next;
2647   }
2648 }
2649
2650 void LoadLevelArtworkInfo()
2651 {
2652   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2653
2654   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
2655   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
2656   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
2657
2658   /* needed for reloading level artwork not known at ealier stage */
2659
2660   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
2661   {
2662     artwork.gfx_current =
2663       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2664     if (artwork.gfx_current == NULL)
2665       artwork.gfx_current =
2666         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2667     if (artwork.gfx_current == NULL)
2668       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2669   }
2670
2671   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
2672   {
2673     artwork.snd_current =
2674       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2675     if (artwork.snd_current == NULL)
2676       artwork.snd_current =
2677         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2678     if (artwork.snd_current == NULL)
2679       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2680   }
2681
2682   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
2683   {
2684     artwork.mus_current =
2685       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2686     if (artwork.mus_current == NULL)
2687       artwork.mus_current =
2688         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2689     if (artwork.mus_current == NULL)
2690       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2691   }
2692
2693   sortTreeInfo(&artwork.gfx_first);
2694   sortTreeInfo(&artwork.snd_first);
2695   sortTreeInfo(&artwork.mus_first);
2696
2697 #if 0
2698   dumpTreeInfo(artwork.gfx_first, 0);
2699   dumpTreeInfo(artwork.snd_first, 0);
2700   dumpTreeInfo(artwork.mus_first, 0);
2701 #endif
2702 }
2703
2704 static void SaveUserLevelInfo()
2705 {
2706   LevelDirTree *level_info;
2707   char *filename;
2708   FILE *file;
2709   int i;
2710
2711   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2712
2713   if (!(file = fopen(filename, MODE_WRITE)))
2714   {
2715     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2716     free(filename);
2717     return;
2718   }
2719
2720   level_info = newTreeInfo();
2721
2722   /* always start with reliable default values */
2723   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2724
2725   setString(&level_info->name, getLoginName());
2726   setString(&level_info->author, getRealName());
2727   level_info->levels = 100;
2728   level_info->first_level = 1;
2729
2730   token_value_position = TOKEN_VALUE_POSITION_SHORT;
2731
2732   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2733                                                  getCookie("LEVELINFO")));
2734
2735   ldi = *level_info;
2736   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2737   {
2738     if (i == LEVELINFO_TOKEN_NAME ||
2739         i == LEVELINFO_TOKEN_AUTHOR ||
2740         i == LEVELINFO_TOKEN_LEVELS ||
2741         i == LEVELINFO_TOKEN_FIRST_LEVEL)
2742       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2743
2744     /* just to make things nicer :) */
2745     if (i == LEVELINFO_TOKEN_AUTHOR)
2746       fprintf(file, "\n");      
2747   }
2748
2749   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
2750
2751   fclose(file);
2752
2753   SetFilePermissions(filename, PERMS_PRIVATE);
2754
2755   freeTreeInfo(level_info);
2756   free(filename);
2757 }
2758
2759 char *getSetupValue(int type, void *value)
2760 {
2761   static char value_string[MAX_LINE_LEN];
2762
2763   if (value == NULL)
2764     return NULL;
2765
2766   switch (type)
2767   {
2768     case TYPE_BOOLEAN:
2769       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2770       break;
2771
2772     case TYPE_SWITCH:
2773       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2774       break;
2775
2776     case TYPE_YES_NO:
2777       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2778       break;
2779
2780     case TYPE_ECS_AGA:
2781       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
2782       break;
2783
2784     case TYPE_KEY:
2785       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2786       break;
2787
2788     case TYPE_KEY_X11:
2789       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2790       break;
2791
2792     case TYPE_INTEGER:
2793       sprintf(value_string, "%d", *(int *)value);
2794       break;
2795
2796     case TYPE_STRING:
2797       strcpy(value_string, *(char **)value);
2798       break;
2799
2800     default:
2801       value_string[0] = '\0';
2802       break;
2803   }
2804
2805   if (type & TYPE_GHOSTED)
2806     strcpy(value_string, "n/a");
2807
2808   return value_string;
2809 }
2810
2811 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2812 {
2813   int i;
2814   char *line;
2815   static char token_string[MAX_LINE_LEN];
2816   int token_type = token_info[token_nr].type;
2817   void *setup_value = token_info[token_nr].value;
2818   char *token_text = token_info[token_nr].text;
2819   char *value_string = getSetupValue(token_type, setup_value);
2820
2821   /* build complete token string */
2822   sprintf(token_string, "%s%s", prefix, token_text);
2823
2824   /* build setup entry line */
2825   line = getFormattedSetupEntry(token_string, value_string);
2826
2827   if (token_type == TYPE_KEY_X11)
2828   {
2829     Key key = *(Key *)setup_value;
2830     char *keyname = getKeyNameFromKey(key);
2831
2832     /* add comment, if useful */
2833     if (!strEqual(keyname, "(undefined)") &&
2834         !strEqual(keyname, "(unknown)"))
2835     {
2836       /* add at least one whitespace */
2837       strcat(line, " ");
2838       for (i = strlen(line); i < token_comment_position; i++)
2839         strcat(line, " ");
2840
2841       strcat(line, "# ");
2842       strcat(line, keyname);
2843     }
2844   }
2845
2846   return line;
2847 }
2848
2849 void LoadLevelSetup_LastSeries()
2850 {
2851   /* ----------------------------------------------------------------------- */
2852   /* ~/.<program>/levelsetup.conf                                            */
2853   /* ----------------------------------------------------------------------- */
2854
2855   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2856   SetupFileHash *level_setup_hash = NULL;
2857
2858   /* always start with reliable default values */
2859   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2860
2861   if ((level_setup_hash = loadSetupFileHash(filename)))
2862   {
2863     char *last_level_series =
2864       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2865
2866     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2867                                                  last_level_series);
2868     if (leveldir_current == NULL)
2869       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2870
2871     checkSetupFileHashIdentifier(level_setup_hash, filename,
2872                                  getCookie("LEVELSETUP"));
2873
2874     freeSetupFileHash(level_setup_hash);
2875   }
2876   else
2877     Error(ERR_WARN, "using default setup values");
2878
2879   free(filename);
2880 }
2881
2882 void SaveLevelSetup_LastSeries()
2883 {
2884   /* ----------------------------------------------------------------------- */
2885   /* ~/.<program>/levelsetup.conf                                            */
2886   /* ----------------------------------------------------------------------- */
2887
2888   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2889   char *level_subdir = leveldir_current->subdir;
2890   FILE *file;
2891
2892   InitUserDataDirectory();
2893
2894   if (!(file = fopen(filename, MODE_WRITE)))
2895   {
2896     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2897     free(filename);
2898     return;
2899   }
2900
2901   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2902                                                  getCookie("LEVELSETUP")));
2903   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2904                                                level_subdir));
2905
2906   fclose(file);
2907
2908   SetFilePermissions(filename, PERMS_PRIVATE);
2909
2910   free(filename);
2911 }
2912
2913 static void checkSeriesInfo()
2914 {
2915   static char *level_directory = NULL;
2916   DIR *dir;
2917   struct dirent *dir_entry;
2918
2919   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2920
2921   level_directory = getPath2((leveldir_current->in_user_dir ?
2922                               getUserLevelDir(NULL) :
2923                               options.level_directory),
2924                              leveldir_current->fullpath);
2925
2926   if ((dir = opendir(level_directory)) == NULL)
2927   {
2928     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2929     return;
2930   }
2931
2932   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2933   {
2934     if (strlen(dir_entry->d_name) > 4 &&
2935         dir_entry->d_name[3] == '.' &&
2936         strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
2937     {
2938       char levelnum_str[4];
2939       int levelnum_value;
2940
2941       strncpy(levelnum_str, dir_entry->d_name, 3);
2942       levelnum_str[3] = '\0';
2943
2944       levelnum_value = atoi(levelnum_str);
2945
2946 #if 0
2947       if (levelnum_value < leveldir_current->first_level)
2948       {
2949         Error(ERR_WARN, "additional level %d found", levelnum_value);
2950         leveldir_current->first_level = levelnum_value;
2951       }
2952       else if (levelnum_value > leveldir_current->last_level)
2953       {
2954         Error(ERR_WARN, "additional level %d found", levelnum_value);
2955         leveldir_current->last_level = levelnum_value;
2956       }
2957 #endif
2958     }
2959   }
2960
2961   closedir(dir);
2962 }
2963
2964 void LoadLevelSetup_SeriesInfo()
2965 {
2966   char *filename;
2967   SetupFileHash *level_setup_hash = NULL;
2968   char *level_subdir = leveldir_current->subdir;
2969
2970   /* always start with reliable default values */
2971   level_nr = leveldir_current->first_level;
2972
2973   checkSeriesInfo(leveldir_current);
2974
2975   /* ----------------------------------------------------------------------- */
2976   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2977   /* ----------------------------------------------------------------------- */
2978
2979   level_subdir = leveldir_current->subdir;
2980
2981   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2982
2983   if ((level_setup_hash = loadSetupFileHash(filename)))
2984   {
2985     char *token_value;
2986
2987     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2988
2989     if (token_value)
2990     {
2991       level_nr = atoi(token_value);
2992
2993       if (level_nr < leveldir_current->first_level)
2994         level_nr = leveldir_current->first_level;
2995       if (level_nr > leveldir_current->last_level)
2996         level_nr = leveldir_current->last_level;
2997     }
2998
2999     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3000
3001     if (token_value)
3002     {
3003       int level_nr = atoi(token_value);
3004
3005       if (level_nr < leveldir_current->first_level)
3006         level_nr = leveldir_current->first_level;
3007       if (level_nr > leveldir_current->last_level + 1)
3008         level_nr = leveldir_current->last_level;
3009
3010       if (leveldir_current->user_defined || !leveldir_current->handicap)
3011         level_nr = leveldir_current->last_level;
3012
3013       leveldir_current->handicap_level = level_nr;
3014     }
3015
3016     checkSetupFileHashIdentifier(level_setup_hash, filename,
3017                                  getCookie("LEVELSETUP"));
3018
3019     freeSetupFileHash(level_setup_hash);
3020   }
3021   else
3022     Error(ERR_WARN, "using default setup values");
3023
3024   free(filename);
3025 }
3026
3027 void SaveLevelSetup_SeriesInfo()
3028 {
3029   char *filename;
3030   char *level_subdir = leveldir_current->subdir;
3031   char *level_nr_str = int2str(level_nr, 0);
3032   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3033   FILE *file;
3034
3035   /* ----------------------------------------------------------------------- */
3036   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
3037   /* ----------------------------------------------------------------------- */
3038
3039   InitLevelSetupDirectory(level_subdir);
3040
3041   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3042
3043   if (!(file = fopen(filename, MODE_WRITE)))
3044   {
3045     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3046     free(filename);
3047     return;
3048   }
3049
3050   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3051                                                  getCookie("LEVELSETUP")));
3052   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3053                                                level_nr_str));
3054   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3055                                                handicap_level_str));
3056
3057   fclose(file);
3058
3059   SetFilePermissions(filename, PERMS_PRIVATE);
3060
3061   free(filename);
3062 }