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