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