2cafc863a7890881244115576091c962407b5a5a
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <dirent.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "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_directory);
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 #if 1
2289   /* after loading all level set information, clone the level directory tree
2290      and remove all level sets without levels (these may still contain artwork
2291      to be offered in the setup menu as "custom artwork", and are therefore
2292      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2293   leveldir_first_all = leveldir_first;
2294   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2295 #endif
2296
2297   AdjustGraphicsForEMC();
2298
2299   /* before sorting, the first entries will be from the user directory */
2300   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2301
2302   if (leveldir_first == NULL)
2303     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2304
2305   sortTreeInfo(&leveldir_first);
2306
2307 #if 0
2308   dumpTreeInfo(leveldir_first, 0);
2309 #endif
2310 }
2311
2312 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2313                                               TreeInfo *node_parent,
2314                                               char *base_directory,
2315                                               char *directory_name, int type)
2316 {
2317   char *directory_path = getPath2(base_directory, directory_name);
2318   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2319   SetupFileHash *setup_file_hash = NULL;
2320   TreeInfo *artwork_new = NULL;
2321   int i;
2322
2323   if (fileExists(filename))
2324     setup_file_hash = loadSetupFileHash(filename);
2325
2326   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2327   {
2328     DIR *dir;
2329     struct dirent *dir_entry;
2330     boolean valid_file_found = FALSE;
2331
2332     if ((dir = opendir(directory_path)) != NULL)
2333     {
2334       while ((dir_entry = readdir(dir)) != NULL)
2335       {
2336         char *entry_name = dir_entry->d_name;
2337
2338         if (FileIsArtworkType(entry_name, type))
2339         {
2340           valid_file_found = TRUE;
2341           break;
2342         }
2343       }
2344
2345       closedir(dir);
2346     }
2347
2348     if (!valid_file_found)
2349     {
2350       if (!strEqual(directory_name, "."))
2351         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2352
2353       free(directory_path);
2354       free(filename);
2355
2356       return FALSE;
2357     }
2358   }
2359
2360   artwork_new = newTreeInfo();
2361
2362   if (node_parent)
2363     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2364   else
2365     setTreeInfoToDefaults(artwork_new, type);
2366
2367   artwork_new->subdir = getStringCopy(directory_name);
2368
2369   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2370   {
2371 #if 0
2372     checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2373 #endif
2374
2375     /* set all structure fields according to the token/value pairs */
2376     ldi = *artwork_new;
2377     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2378       setSetupInfo(levelinfo_tokens, i,
2379                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2380     *artwork_new = ldi;
2381
2382     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2383       setString(&artwork_new->name, artwork_new->subdir);
2384
2385 #if 0
2386     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2387 #endif
2388
2389     if (artwork_new->identifier == NULL)
2390       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2391
2392     if (artwork_new->name_sorting == NULL)
2393       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2394   }
2395
2396   if (node_parent == NULL)              /* top level group */
2397   {
2398     artwork_new->basepath = getStringCopy(base_directory);
2399     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2400   }
2401   else                                  /* sub level group */
2402   {
2403     artwork_new->basepath = getStringCopy(node_parent->basepath);
2404     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2405   }
2406
2407   artwork_new->in_user_dir =
2408     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2409
2410   /* (may use ".sort_priority" from "setup_file_hash" above) */
2411   artwork_new->color = ARTWORKCOLOR(artwork_new);
2412
2413   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2414
2415   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2416   {
2417     if (strEqual(artwork_new->subdir, "."))
2418     {
2419       if (artwork_new->user_defined)
2420       {
2421         setString(&artwork_new->identifier, "private");
2422         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2423       }
2424       else
2425       {
2426         setString(&artwork_new->identifier, "classic");
2427         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2428       }
2429
2430       /* set to new values after changing ".sort_priority" */
2431       artwork_new->color = ARTWORKCOLOR(artwork_new);
2432
2433       setString(&artwork_new->class_desc,
2434                 getLevelClassDescription(artwork_new));
2435     }
2436     else
2437     {
2438       setString(&artwork_new->identifier, artwork_new->subdir);
2439     }
2440
2441     setString(&artwork_new->name, artwork_new->identifier);
2442     setString(&artwork_new->name_sorting, artwork_new->name);
2443   }
2444
2445   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2446
2447   pushTreeInfo(node_first, artwork_new);
2448
2449   freeSetupFileHash(setup_file_hash);
2450
2451   free(directory_path);
2452   free(filename);
2453
2454   return TRUE;
2455 }
2456
2457 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2458                                           TreeInfo *node_parent,
2459                                           char *base_directory, int type)
2460 {
2461   DIR *dir;
2462   struct dirent *dir_entry;
2463   boolean valid_entry_found = FALSE;
2464
2465   if ((dir = opendir(base_directory)) == NULL)
2466   {
2467     /* display error if directory is main "options.graphics_directory" etc. */
2468     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2469       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2470
2471     return;
2472   }
2473
2474   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2475   {
2476     struct stat file_status;
2477     char *directory_name = dir_entry->d_name;
2478     char *directory_path = getPath2(base_directory, directory_name);
2479
2480     /* skip directory entries for current and parent directory */
2481     if (strEqual(directory_name, ".") ||
2482         strEqual(directory_name, ".."))
2483     {
2484       free(directory_path);
2485       continue;
2486     }
2487
2488     /* skip directory entries which are not a directory or are not accessible */
2489     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2490         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2491     {
2492       free(directory_path);
2493       continue;
2494     }
2495
2496     free(directory_path);
2497
2498     /* check if this directory contains artwork with or without config file */
2499     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2500                                                         base_directory,
2501                                                         directory_name, type);
2502   }
2503
2504   closedir(dir);
2505
2506   /* check if this directory directly contains artwork itself */
2507   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2508                                                       base_directory, ".",
2509                                                       type);
2510   if (!valid_entry_found)
2511     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2512           base_directory);
2513 }
2514
2515 static TreeInfo *getDummyArtworkInfo(int type)
2516 {
2517   /* this is only needed when there is completely no artwork available */
2518   TreeInfo *artwork_new = newTreeInfo();
2519
2520   setTreeInfoToDefaults(artwork_new, type);
2521
2522   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
2523   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2524   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2525
2526   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2527   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2528   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2529
2530   return artwork_new;
2531 }
2532
2533 void LoadArtworkInfo()
2534 {
2535   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2536
2537   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2538                                 options.graphics_directory,
2539                                 TREE_TYPE_GRAPHICS_DIR);
2540   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2541                                 getUserGraphicsDir(),
2542                                 TREE_TYPE_GRAPHICS_DIR);
2543
2544   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2545                                 options.sounds_directory,
2546                                 TREE_TYPE_SOUNDS_DIR);
2547   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2548                                 getUserSoundsDir(),
2549                                 TREE_TYPE_SOUNDS_DIR);
2550
2551   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2552                                 options.music_directory,
2553                                 TREE_TYPE_MUSIC_DIR);
2554   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2555                                 getUserMusicDir(),
2556                                 TREE_TYPE_MUSIC_DIR);
2557
2558   if (artwork.gfx_first == NULL)
2559     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2560   if (artwork.snd_first == NULL)
2561     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2562   if (artwork.mus_first == NULL)
2563     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2564
2565   /* before sorting, the first entries will be from the user directory */
2566   artwork.gfx_current =
2567     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2568   if (artwork.gfx_current == NULL)
2569     artwork.gfx_current =
2570       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2571   if (artwork.gfx_current == NULL)
2572     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2573
2574   artwork.snd_current =
2575     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2576   if (artwork.snd_current == NULL)
2577     artwork.snd_current =
2578       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2579   if (artwork.snd_current == NULL)
2580     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2581
2582   artwork.mus_current =
2583     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2584   if (artwork.mus_current == NULL)
2585     artwork.mus_current =
2586       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2587   if (artwork.mus_current == NULL)
2588     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2589
2590   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2591   artwork.snd_current_identifier = artwork.snd_current->identifier;
2592   artwork.mus_current_identifier = artwork.mus_current->identifier;
2593
2594 #if 0
2595   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2596   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2597   printf("music set == %s\n\n", artwork.mus_current_identifier);
2598 #endif
2599
2600   sortTreeInfo(&artwork.gfx_first);
2601   sortTreeInfo(&artwork.snd_first);
2602   sortTreeInfo(&artwork.mus_first);
2603
2604 #if 0
2605   dumpTreeInfo(artwork.gfx_first, 0);
2606   dumpTreeInfo(artwork.snd_first, 0);
2607   dumpTreeInfo(artwork.mus_first, 0);
2608 #endif
2609 }
2610
2611 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2612                                   LevelDirTree *level_node)
2613 {
2614   /* recursively check all level directories for artwork sub-directories */
2615
2616   while (level_node)
2617   {
2618     /* check all tree entries for artwork, but skip parent link entries */
2619     if (!level_node->parent_link)
2620     {
2621       TreeInfo *topnode_last = *artwork_node;
2622       char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2623                             ARTWORK_DIRECTORY((*artwork_node)->type));
2624
2625       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2626                                     (*artwork_node)->type);
2627
2628       if (topnode_last != *artwork_node)
2629       {
2630         free((*artwork_node)->identifier);
2631         free((*artwork_node)->name);
2632         free((*artwork_node)->name_sorting);
2633
2634         (*artwork_node)->identifier   = getStringCopy(level_node->subdir);
2635         (*artwork_node)->name         = getStringCopy(level_node->name);
2636         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2637
2638         (*artwork_node)->sort_priority = level_node->sort_priority;
2639         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2640       }
2641
2642       free(path);
2643     }
2644
2645     if (level_node->node_group != NULL)
2646       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2647
2648     level_node = level_node->next;
2649   }
2650 }
2651
2652 void LoadLevelArtworkInfo()
2653 {
2654   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2655
2656   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
2657   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
2658   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
2659
2660   /* needed for reloading level artwork not known at ealier stage */
2661
2662   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
2663   {
2664     artwork.gfx_current =
2665       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2666     if (artwork.gfx_current == NULL)
2667       artwork.gfx_current =
2668         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2669     if (artwork.gfx_current == NULL)
2670       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2671   }
2672
2673   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
2674   {
2675     artwork.snd_current =
2676       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2677     if (artwork.snd_current == NULL)
2678       artwork.snd_current =
2679         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2680     if (artwork.snd_current == NULL)
2681       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2682   }
2683
2684   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
2685   {
2686     artwork.mus_current =
2687       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2688     if (artwork.mus_current == NULL)
2689       artwork.mus_current =
2690         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2691     if (artwork.mus_current == NULL)
2692       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2693   }
2694
2695   sortTreeInfo(&artwork.gfx_first);
2696   sortTreeInfo(&artwork.snd_first);
2697   sortTreeInfo(&artwork.mus_first);
2698
2699 #if 0
2700   dumpTreeInfo(artwork.gfx_first, 0);
2701   dumpTreeInfo(artwork.snd_first, 0);
2702   dumpTreeInfo(artwork.mus_first, 0);
2703 #endif
2704 }
2705
2706 static void SaveUserLevelInfo()
2707 {
2708   LevelDirTree *level_info;
2709   char *filename;
2710   FILE *file;
2711   int i;
2712
2713   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2714
2715   if (!(file = fopen(filename, MODE_WRITE)))
2716   {
2717     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2718     free(filename);
2719     return;
2720   }
2721
2722   level_info = newTreeInfo();
2723
2724   /* always start with reliable default values */
2725   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2726
2727   setString(&level_info->name, getLoginName());
2728   setString(&level_info->author, getRealName());
2729   level_info->levels = 100;
2730   level_info->first_level = 1;
2731
2732   token_value_position = TOKEN_VALUE_POSITION_SHORT;
2733
2734   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2735                                                  getCookie("LEVELINFO")));
2736
2737   ldi = *level_info;
2738   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2739   {
2740     if (i == LEVELINFO_TOKEN_NAME ||
2741         i == LEVELINFO_TOKEN_AUTHOR ||
2742         i == LEVELINFO_TOKEN_LEVELS ||
2743         i == LEVELINFO_TOKEN_FIRST_LEVEL)
2744       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2745
2746     /* just to make things nicer :) */
2747     if (i == LEVELINFO_TOKEN_AUTHOR)
2748       fprintf(file, "\n");      
2749   }
2750
2751   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
2752
2753   fclose(file);
2754
2755   SetFilePermissions(filename, PERMS_PRIVATE);
2756
2757   freeTreeInfo(level_info);
2758   free(filename);
2759 }
2760
2761 char *getSetupValue(int type, void *value)
2762 {
2763   static char value_string[MAX_LINE_LEN];
2764
2765   if (value == NULL)
2766     return NULL;
2767
2768   switch (type)
2769   {
2770     case TYPE_BOOLEAN:
2771       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2772       break;
2773
2774     case TYPE_SWITCH:
2775       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2776       break;
2777
2778     case TYPE_YES_NO:
2779       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2780       break;
2781
2782     case TYPE_ECS_AGA:
2783       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
2784       break;
2785
2786     case TYPE_KEY:
2787       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2788       break;
2789
2790     case TYPE_KEY_X11:
2791       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2792       break;
2793
2794     case TYPE_INTEGER:
2795       sprintf(value_string, "%d", *(int *)value);
2796       break;
2797
2798     case TYPE_STRING:
2799       strcpy(value_string, *(char **)value);
2800       break;
2801
2802     default:
2803       value_string[0] = '\0';
2804       break;
2805   }
2806
2807   if (type & TYPE_GHOSTED)
2808     strcpy(value_string, "n/a");
2809
2810   return value_string;
2811 }
2812
2813 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2814 {
2815   int i;
2816   char *line;
2817   static char token_string[MAX_LINE_LEN];
2818   int token_type = token_info[token_nr].type;
2819   void *setup_value = token_info[token_nr].value;
2820   char *token_text = token_info[token_nr].text;
2821   char *value_string = getSetupValue(token_type, setup_value);
2822
2823   /* build complete token string */
2824   sprintf(token_string, "%s%s", prefix, token_text);
2825
2826   /* build setup entry line */
2827   line = getFormattedSetupEntry(token_string, value_string);
2828
2829   if (token_type == TYPE_KEY_X11)
2830   {
2831     Key key = *(Key *)setup_value;
2832     char *keyname = getKeyNameFromKey(key);
2833
2834     /* add comment, if useful */
2835     if (!strEqual(keyname, "(undefined)") &&
2836         !strEqual(keyname, "(unknown)"))
2837     {
2838       /* add at least one whitespace */
2839       strcat(line, " ");
2840       for (i = strlen(line); i < token_comment_position; i++)
2841         strcat(line, " ");
2842
2843       strcat(line, "# ");
2844       strcat(line, keyname);
2845     }
2846   }
2847
2848   return line;
2849 }
2850
2851 void LoadLevelSetup_LastSeries()
2852 {
2853   /* ----------------------------------------------------------------------- */
2854   /* ~/.<program>/levelsetup.conf                                            */
2855   /* ----------------------------------------------------------------------- */
2856
2857   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2858   SetupFileHash *level_setup_hash = NULL;
2859
2860   /* always start with reliable default values */
2861   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2862
2863   if ((level_setup_hash = loadSetupFileHash(filename)))
2864   {
2865     char *last_level_series =
2866       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2867
2868     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2869                                                  last_level_series);
2870     if (leveldir_current == NULL)
2871       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2872
2873     checkSetupFileHashIdentifier(level_setup_hash, filename,
2874                                  getCookie("LEVELSETUP"));
2875
2876     freeSetupFileHash(level_setup_hash);
2877   }
2878   else
2879     Error(ERR_WARN, "using default setup values");
2880
2881   free(filename);
2882 }
2883
2884 void SaveLevelSetup_LastSeries()
2885 {
2886   /* ----------------------------------------------------------------------- */
2887   /* ~/.<program>/levelsetup.conf                                            */
2888   /* ----------------------------------------------------------------------- */
2889
2890   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2891   char *level_subdir = leveldir_current->subdir;
2892   FILE *file;
2893
2894   InitUserDataDirectory();
2895
2896   if (!(file = fopen(filename, MODE_WRITE)))
2897   {
2898     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2899     free(filename);
2900     return;
2901   }
2902
2903   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2904                                                  getCookie("LEVELSETUP")));
2905   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2906                                                level_subdir));
2907
2908   fclose(file);
2909
2910   SetFilePermissions(filename, PERMS_PRIVATE);
2911
2912   free(filename);
2913 }
2914
2915 static void checkSeriesInfo()
2916 {
2917   static char *level_directory = NULL;
2918   DIR *dir;
2919   struct dirent *dir_entry;
2920
2921   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2922
2923   level_directory = getPath2((leveldir_current->in_user_dir ?
2924                               getUserLevelDir(NULL) :
2925                               options.level_directory),
2926                              leveldir_current->fullpath);
2927
2928   if ((dir = opendir(level_directory)) == NULL)
2929   {
2930     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2931     return;
2932   }
2933
2934   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2935   {
2936     if (strlen(dir_entry->d_name) > 4 &&
2937         dir_entry->d_name[3] == '.' &&
2938         strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
2939     {
2940       char levelnum_str[4];
2941       int levelnum_value;
2942
2943       strncpy(levelnum_str, dir_entry->d_name, 3);
2944       levelnum_str[3] = '\0';
2945
2946       levelnum_value = atoi(levelnum_str);
2947
2948 #if 0
2949       if (levelnum_value < leveldir_current->first_level)
2950       {
2951         Error(ERR_WARN, "additional level %d found", levelnum_value);
2952         leveldir_current->first_level = levelnum_value;
2953       }
2954       else if (levelnum_value > leveldir_current->last_level)
2955       {
2956         Error(ERR_WARN, "additional level %d found", levelnum_value);
2957         leveldir_current->last_level = levelnum_value;
2958       }
2959 #endif
2960     }
2961   }
2962
2963   closedir(dir);
2964 }
2965
2966 void LoadLevelSetup_SeriesInfo()
2967 {
2968   char *filename;
2969   SetupFileHash *level_setup_hash = NULL;
2970   char *level_subdir = leveldir_current->subdir;
2971
2972   /* always start with reliable default values */
2973   level_nr = leveldir_current->first_level;
2974
2975   checkSeriesInfo(leveldir_current);
2976
2977   /* ----------------------------------------------------------------------- */
2978   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2979   /* ----------------------------------------------------------------------- */
2980
2981   level_subdir = leveldir_current->subdir;
2982
2983   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2984
2985   if ((level_setup_hash = loadSetupFileHash(filename)))
2986   {
2987     char *token_value;
2988
2989     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2990
2991     if (token_value)
2992     {
2993       level_nr = atoi(token_value);
2994
2995       if (level_nr < leveldir_current->first_level)
2996         level_nr = leveldir_current->first_level;
2997       if (level_nr > leveldir_current->last_level)
2998         level_nr = leveldir_current->last_level;
2999     }
3000
3001     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3002
3003     if (token_value)
3004     {
3005       int level_nr = atoi(token_value);
3006
3007       if (level_nr < leveldir_current->first_level)
3008         level_nr = leveldir_current->first_level;
3009       if (level_nr > leveldir_current->last_level + 1)
3010         level_nr = leveldir_current->last_level;
3011
3012       if (leveldir_current->user_defined || !leveldir_current->handicap)
3013         level_nr = leveldir_current->last_level;
3014
3015       leveldir_current->handicap_level = level_nr;
3016     }
3017
3018     checkSetupFileHashIdentifier(level_setup_hash, filename,
3019                                  getCookie("LEVELSETUP"));
3020
3021     freeSetupFileHash(level_setup_hash);
3022   }
3023   else
3024     Error(ERR_WARN, "using default setup values");
3025
3026   free(filename);
3027 }
3028
3029 void SaveLevelSetup_SeriesInfo()
3030 {
3031   char *filename;
3032   char *level_subdir = leveldir_current->subdir;
3033   char *level_nr_str = int2str(level_nr, 0);
3034   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3035   FILE *file;
3036
3037   /* ----------------------------------------------------------------------- */
3038   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
3039   /* ----------------------------------------------------------------------- */
3040
3041   InitLevelSetupDirectory(level_subdir);
3042
3043   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3044
3045   if (!(file = fopen(filename, MODE_WRITE)))
3046   {
3047     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3048     free(filename);
3049     return;
3050   }
3051
3052   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3053                                                  getCookie("LEVELSETUP")));
3054   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3055                                                level_nr_str));
3056   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3057                                                handicap_level_str));
3058
3059   fclose(file);
3060
3061   SetFilePermissions(filename, PERMS_PRIVATE);
3062
3063   free(filename);
3064 }