rnd-20070302-1-src
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <dirent.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "platform.h"
21
22 #if !defined(PLATFORM_WIN32)
23 #include <pwd.h>
24 #include <sys/param.h>
25 #endif
26
27 #include "setup.h"
28 #include "joystick.h"
29 #include "text.h"
30 #include "misc.h"
31 #include "hash.h"
32
33
34 #define NUM_LEVELCLASS_DESC     8
35
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
37 {
38   "Tutorial Levels",
39   "Classic Originals",
40   "Contributions",
41   "Private Levels",
42   "Boulderdash",
43   "Emerald Mine",
44   "Supaplex",
45   "DX Boulderdash"
46 };
47
48
49 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
50                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
51                          IS_LEVELCLASS_BD(n) ?                  FC_YELLOW :  \
52                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
53                          IS_LEVELCLASS_SP(n) ?                  FC_YELLOW :  \
54                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
55                          IS_LEVELCLASS_SB(n) ?                  FC_YELLOW :  \
56                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
57                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
58                          FC_BLUE)
59
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
61                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
62                          IS_LEVELCLASS_BD(n) ?                  2 :     \
63                          IS_LEVELCLASS_EM(n) ?                  3 :     \
64                          IS_LEVELCLASS_SP(n) ?                  4 :     \
65                          IS_LEVELCLASS_DX(n) ?                  5 :     \
66                          IS_LEVELCLASS_SB(n) ?                  6 :     \
67                          IS_LEVELCLASS_CONTRIB(n) ?             7 :     \
68                          IS_LEVELCLASS_PRIVATE(n) ?             8 :     \
69                          9)
70
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
72                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_GREEN :   \
73                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
74                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_YELLOW :  \
75                          FC_BLUE)
76
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
78                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
79                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
80                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
81                            9)
82
83 #define TOKEN_VALUE_POSITION_SHORT              32
84 #define TOKEN_VALUE_POSITION_DEFAULT            40
85 #define TOKEN_COMMENT_POSITION_DEFAULT          60
86
87 #define MAX_COOKIE_LEN                          256
88
89
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(void *setup_file_data, char *filename,
1614                               boolean top_recursion_level, boolean is_hash)
1615 {
1616   static SetupFileHash *include_filename_hash = NULL;
1617   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1618   char *token, *value, *line_ptr;
1619   void *insert_ptr = NULL;
1620   boolean read_continued_line = FALSE;
1621   boolean token_value_separator_found;
1622 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1623   boolean token_value_separator_warning = FALSE;
1624 #endif
1625   FILE *file;
1626   int line_nr = 0;
1627   int token_count = 0;
1628
1629   if (!(file = fopen(filename, MODE_READ)))
1630   {
1631     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1632
1633     return;
1634   }
1635
1636   /* use "insert pointer" to store list end for constant insertion complexity */
1637   if (!is_hash)
1638     insert_ptr = setup_file_data;
1639
1640   /* on top invocation, create hash to mark included files (to prevent loops) */
1641   if (top_recursion_level)
1642     include_filename_hash = newSetupFileHash();
1643
1644   /* mark this file as already included (to prevent including it again) */
1645   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1646
1647   while (!feof(file))
1648   {
1649     /* read next line of input file */
1650     if (!fgets(line, MAX_LINE_LEN, file))
1651       break;
1652
1653     /* check if line was completely read and is terminated by line break */
1654     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1655       line_nr++;
1656
1657     /* cut trailing line break (this can be newline and/or carriage return) */
1658     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1659       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1660         *line_ptr = '\0';
1661
1662     /* copy raw input line for later use (mainly debugging output) */
1663     strcpy(line_raw, line);
1664
1665     if (read_continued_line)
1666     {
1667       /* cut leading whitespaces from input line */
1668       for (line_ptr = line; *line_ptr; line_ptr++)
1669         if (*line_ptr != ' ' && *line_ptr != '\t')
1670           break;
1671
1672       /* append new line to existing line, if there is enough space */
1673       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1674         strcat(previous_line, line_ptr);
1675
1676       strcpy(line, previous_line);      /* copy storage buffer to line */
1677
1678       read_continued_line = FALSE;
1679     }
1680
1681     /* if the last character is '\', continue at next line */
1682     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1683     {
1684       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
1685       strcpy(previous_line, line);      /* copy line to storage buffer */
1686
1687       read_continued_line = TRUE;
1688
1689       continue;
1690     }
1691
1692     /* cut trailing comment from input line */
1693     for (line_ptr = line; *line_ptr; line_ptr++)
1694     {
1695       if (*line_ptr == '#')
1696       {
1697         *line_ptr = '\0';
1698         break;
1699       }
1700     }
1701
1702     /* cut trailing whitespaces from input line */
1703     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1704       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1705         *line_ptr = '\0';
1706
1707     /* ignore empty lines */
1708     if (*line == '\0')
1709       continue;
1710
1711     /* cut leading whitespaces from token */
1712     for (token = line; *token; token++)
1713       if (*token != ' ' && *token != '\t')
1714         break;
1715
1716     /* start with empty value as reliable default */
1717     value = "";
1718
1719     token_value_separator_found = FALSE;
1720
1721     /* find end of token to determine start of value */
1722     for (line_ptr = token; *line_ptr; line_ptr++)
1723     {
1724 #if 1
1725       /* first look for an explicit token/value separator, like ':' or '=' */
1726       if (*line_ptr == ':' || *line_ptr == '=')
1727 #else
1728       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1729 #endif
1730       {
1731         *line_ptr = '\0';               /* terminate token string */
1732         value = line_ptr + 1;           /* set beginning of value */
1733
1734         token_value_separator_found = TRUE;
1735
1736         break;
1737       }
1738     }
1739
1740 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1741     /* fallback: if no token/value separator found, also allow whitespaces */
1742     if (!token_value_separator_found)
1743     {
1744       for (line_ptr = token; *line_ptr; line_ptr++)
1745       {
1746         if (*line_ptr == ' ' || *line_ptr == '\t')
1747         {
1748           *line_ptr = '\0';             /* terminate token string */
1749           value = line_ptr + 1;         /* set beginning of value */
1750
1751           token_value_separator_found = TRUE;
1752
1753           break;
1754         }
1755       }
1756
1757 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1758       if (token_value_separator_found)
1759       {
1760         if (!token_value_separator_warning)
1761         {
1762           Error(ERR_INFO_LINE, "-");
1763           Error(ERR_WARN, "missing token/value separator(s) in config file:");
1764           Error(ERR_INFO, "- config file: '%s'", filename);
1765
1766           token_value_separator_warning = TRUE;
1767         }
1768
1769         Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1770       }
1771 #endif
1772     }
1773 #endif
1774
1775     /* cut trailing whitespaces from token */
1776     for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1777       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1778         *line_ptr = '\0';
1779
1780     /* cut leading whitespaces from value */
1781     for (; *value; value++)
1782       if (*value != ' ' && *value != '\t')
1783         break;
1784
1785 #if 0
1786     if (*value == '\0')
1787       value = "true";   /* treat tokens without value as "true" */
1788 #endif
1789
1790     if (*token)
1791     {
1792       if (strEqual(token, "include"))
1793       {
1794         if (getHashEntry(include_filename_hash, value) == NULL)
1795         {
1796           char *basepath = getBasePath(filename);
1797           char *basename = getBaseName(value);
1798           char *filename_include = getPath2(basepath, basename);
1799
1800 #if 0
1801           Error(ERR_INFO, "[including file '%s']", filename_include);
1802 #endif
1803
1804           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1805
1806           free(basepath);
1807           free(basename);
1808           free(filename_include);
1809         }
1810         else
1811         {
1812           Error(ERR_WARN, "ignoring already processed file '%s'", value);
1813         }
1814       }
1815       else
1816       {
1817         if (is_hash)
1818           setHashEntry((SetupFileHash *)setup_file_data, token, value);
1819         else
1820           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1821
1822         token_count++;
1823       }
1824     }
1825   }
1826
1827   fclose(file);
1828
1829 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1830   if (token_value_separator_warning)
1831     Error(ERR_INFO_LINE, "-");
1832 #endif
1833
1834   if (token_count == 0)
1835     Error(ERR_WARN, "configuration file '%s' is empty", filename);
1836
1837   if (top_recursion_level)
1838     freeSetupFileHash(include_filename_hash);
1839 }
1840
1841 void saveSetupFileHash(SetupFileHash *hash, char *filename)
1842 {
1843   FILE *file;
1844
1845   if (!(file = fopen(filename, MODE_WRITE)))
1846   {
1847     Error(ERR_WARN, "cannot write configuration file '%s'", filename);
1848
1849     return;
1850   }
1851
1852   BEGIN_HASH_ITERATION(hash, itr)
1853   {
1854     fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
1855                                                  HASH_ITERATION_VALUE(itr)));
1856   }
1857   END_HASH_ITERATION(hash, itr)
1858
1859   fclose(file);
1860 }
1861
1862 SetupFileList *loadSetupFileList(char *filename)
1863 {
1864   SetupFileList *setup_file_list = newSetupFileList("", "");
1865   SetupFileList *first_valid_list_entry;
1866
1867   loadSetupFileData(setup_file_list, filename, TRUE, FALSE);
1868
1869   first_valid_list_entry = setup_file_list->next;
1870
1871   /* free empty list header */
1872   setup_file_list->next = NULL;
1873   freeSetupFileList(setup_file_list);
1874
1875   return first_valid_list_entry;
1876 }
1877
1878 SetupFileHash *loadSetupFileHash(char *filename)
1879 {
1880   SetupFileHash *setup_file_hash = newSetupFileHash();
1881
1882   loadSetupFileData(setup_file_hash, filename, TRUE, TRUE);
1883
1884   return setup_file_hash;
1885 }
1886
1887 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1888                                   char *filename, char *identifier)
1889 {
1890   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1891
1892   if (value == NULL)
1893     Error(ERR_WARN, "config file '%s' has no file identifier", filename);
1894   else if (!checkCookieString(value, identifier))
1895     Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
1896 }
1897
1898
1899 /* ========================================================================= */
1900 /* setup file stuff                                                          */
1901 /* ========================================================================= */
1902
1903 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
1904 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
1905 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
1906
1907 /* level directory info */
1908 #define LEVELINFO_TOKEN_IDENTIFIER              0
1909 #define LEVELINFO_TOKEN_NAME                    1
1910 #define LEVELINFO_TOKEN_NAME_SORTING            2
1911 #define LEVELINFO_TOKEN_AUTHOR                  3
1912 #define LEVELINFO_TOKEN_IMPORTED_FROM           4
1913 #define LEVELINFO_TOKEN_IMPORTED_BY             5
1914 #define LEVELINFO_TOKEN_LEVELS                  6
1915 #define LEVELINFO_TOKEN_FIRST_LEVEL             7
1916 #define LEVELINFO_TOKEN_SORT_PRIORITY           8
1917 #define LEVELINFO_TOKEN_LATEST_ENGINE           9
1918 #define LEVELINFO_TOKEN_LEVEL_GROUP             10
1919 #define LEVELINFO_TOKEN_READONLY                11
1920 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        12
1921 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        13
1922 #define LEVELINFO_TOKEN_GRAPHICS_SET            14
1923 #define LEVELINFO_TOKEN_SOUNDS_SET              15
1924 #define LEVELINFO_TOKEN_MUSIC_SET               16
1925 #define LEVELINFO_TOKEN_FILENAME                17
1926 #define LEVELINFO_TOKEN_FILETYPE                18
1927 #define LEVELINFO_TOKEN_HANDICAP                19
1928 #define LEVELINFO_TOKEN_SKIP_LEVELS             20
1929
1930 #define NUM_LEVELINFO_TOKENS                    21
1931
1932 static LevelDirTree ldi;
1933
1934 static struct TokenInfo levelinfo_tokens[] =
1935 {
1936   /* level directory info */
1937   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
1938   { TYPE_STRING,        &ldi.name,              "name"                  },
1939   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
1940   { TYPE_STRING,        &ldi.author,            "author"                },
1941   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
1942   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
1943   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
1944   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
1945   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
1946   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
1947   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
1948   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
1949   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
1950   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
1951   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
1952   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
1953   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
1954   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
1955   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
1956   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
1957   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           }
1958 };
1959
1960 static struct TokenInfo artworkinfo_tokens[] =
1961 {
1962   /* artwork directory info */
1963   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
1964   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
1965   { TYPE_STRING,        &ldi.name,              "name"                  },
1966   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
1967   { TYPE_STRING,        &ldi.author,            "author"                },
1968   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
1969   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
1970   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
1971   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
1972   { TYPE_INTEGER,       &ldi.color,             "color"                 },
1973   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
1974
1975   { -1,                 NULL,                   NULL                    },
1976 };
1977
1978 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
1979 {
1980   ti->type = type;
1981
1982   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
1983                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1984                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
1985                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
1986                   NULL);
1987
1988   ti->node_parent = NULL;
1989   ti->node_group = NULL;
1990   ti->next = NULL;
1991
1992   ti->cl_first = -1;
1993   ti->cl_cursor = -1;
1994
1995   ti->subdir = NULL;
1996   ti->fullpath = NULL;
1997   ti->basepath = NULL;
1998   ti->identifier = NULL;
1999   ti->name = getStringCopy(ANONYMOUS_NAME);
2000   ti->name_sorting = NULL;
2001   ti->author = getStringCopy(ANONYMOUS_NAME);
2002
2003   ti->sort_priority = LEVELCLASS_UNDEFINED;     /* default: least priority */
2004   ti->latest_engine = FALSE;                    /* default: get from level */
2005   ti->parent_link = FALSE;
2006   ti->in_user_dir = FALSE;
2007   ti->user_defined = FALSE;
2008   ti->color = 0;
2009   ti->class_desc = NULL;
2010
2011   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2012
2013   if (ti->type == TREE_TYPE_LEVEL_DIR)
2014   {
2015     ti->imported_from = NULL;
2016     ti->imported_by = NULL;
2017
2018     ti->graphics_set_ecs = NULL;
2019     ti->graphics_set_aga = NULL;
2020     ti->graphics_set = NULL;
2021     ti->sounds_set = NULL;
2022     ti->music_set = NULL;
2023     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2024     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2025     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2026
2027     ti->level_filename = NULL;
2028     ti->level_filetype = NULL;
2029
2030     ti->levels = 0;
2031     ti->first_level = 0;
2032     ti->last_level = 0;
2033     ti->level_group = FALSE;
2034     ti->handicap_level = 0;
2035     ti->readonly = TRUE;
2036     ti->handicap = TRUE;
2037     ti->skip_levels = FALSE;
2038   }
2039 }
2040
2041 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2042 {
2043   if (parent == NULL)
2044   {
2045     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2046
2047     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2048
2049     return;
2050   }
2051
2052   /* copy all values from the parent structure */
2053
2054   ti->type = parent->type;
2055
2056   ti->node_top = parent->node_top;
2057   ti->node_parent = parent;
2058   ti->node_group = NULL;
2059   ti->next = NULL;
2060
2061   ti->cl_first = -1;
2062   ti->cl_cursor = -1;
2063
2064   ti->subdir = NULL;
2065   ti->fullpath = NULL;
2066   ti->basepath = NULL;
2067   ti->identifier = NULL;
2068   ti->name = getStringCopy(ANONYMOUS_NAME);
2069   ti->name_sorting = NULL;
2070   ti->author = getStringCopy(parent->author);
2071
2072   ti->sort_priority = parent->sort_priority;
2073   ti->latest_engine = parent->latest_engine;
2074   ti->parent_link = FALSE;
2075   ti->in_user_dir = parent->in_user_dir;
2076   ti->user_defined = parent->user_defined;
2077   ti->color = parent->color;
2078   ti->class_desc = getStringCopy(parent->class_desc);
2079
2080   ti->infotext = getStringCopy(parent->infotext);
2081
2082   if (ti->type == TREE_TYPE_LEVEL_DIR)
2083   {
2084     ti->imported_from = getStringCopy(parent->imported_from);
2085     ti->imported_by = getStringCopy(parent->imported_by);
2086
2087     ti->graphics_set_ecs = NULL;
2088     ti->graphics_set_aga = NULL;
2089     ti->graphics_set = NULL;
2090     ti->sounds_set = NULL;
2091     ti->music_set = NULL;
2092     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2093     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2094     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2095
2096     ti->level_filename = NULL;
2097     ti->level_filetype = NULL;
2098
2099     ti->levels = 0;
2100     ti->first_level = 0;
2101     ti->last_level = 0;
2102     ti->level_group = FALSE;
2103     ti->handicap_level = 0;
2104     ti->readonly = TRUE;
2105     ti->handicap = TRUE;
2106     ti->skip_levels = FALSE;
2107   }
2108 }
2109
2110 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2111 {
2112   TreeInfo *ti_copy = newTreeInfo();
2113
2114   /* copy all values from the original structure */
2115
2116   ti_copy->type                 = ti->type;
2117
2118   ti_copy->node_top             = ti->node_top;
2119   ti_copy->node_parent          = ti->node_parent;
2120   ti_copy->node_group           = ti->node_group;
2121   ti_copy->next                 = ti->next;
2122
2123   ti_copy->cl_first             = ti->cl_first;
2124   ti_copy->cl_cursor            = ti->cl_cursor;
2125
2126   ti_copy->subdir               = getStringCopy(ti->subdir);
2127   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2128   ti_copy->basepath             = getStringCopy(ti->basepath);
2129   ti_copy->identifier           = getStringCopy(ti->identifier);
2130   ti_copy->name                 = getStringCopy(ti->name);
2131   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2132   ti_copy->author               = getStringCopy(ti->author);
2133   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2134   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2135
2136   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2137   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2138   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2139   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2140   ti_copy->music_set            = getStringCopy(ti->music_set);
2141   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2142   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2143   ti_copy->music_path           = getStringCopy(ti->music_path);
2144
2145   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2146   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2147
2148   ti_copy->levels               = ti->levels;
2149   ti_copy->first_level          = ti->first_level;
2150   ti_copy->last_level           = ti->last_level;
2151   ti_copy->sort_priority        = ti->sort_priority;
2152
2153   ti_copy->latest_engine        = ti->latest_engine;
2154
2155   ti_copy->level_group          = ti->level_group;
2156   ti_copy->parent_link          = ti->parent_link;
2157   ti_copy->in_user_dir          = ti->in_user_dir;
2158   ti_copy->user_defined         = ti->user_defined;
2159   ti_copy->readonly             = ti->readonly;
2160   ti_copy->handicap             = ti->handicap;
2161   ti_copy->skip_levels          = ti->skip_levels;
2162
2163   ti_copy->color                = ti->color;
2164   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2165   ti_copy->handicap_level       = ti->handicap_level;
2166
2167   ti_copy->infotext             = getStringCopy(ti->infotext);
2168
2169   return ti_copy;
2170 }
2171
2172 static void freeTreeInfo(TreeInfo *ti)
2173 {
2174   if (ti == NULL)
2175     return;
2176
2177   checked_free(ti->subdir);
2178   checked_free(ti->fullpath);
2179   checked_free(ti->basepath);
2180   checked_free(ti->identifier);
2181
2182   checked_free(ti->name);
2183   checked_free(ti->name_sorting);
2184   checked_free(ti->author);
2185
2186   checked_free(ti->class_desc);
2187
2188   checked_free(ti->infotext);
2189
2190   if (ti->type == TREE_TYPE_LEVEL_DIR)
2191   {
2192     checked_free(ti->imported_from);
2193     checked_free(ti->imported_by);
2194
2195     checked_free(ti->graphics_set_ecs);
2196     checked_free(ti->graphics_set_aga);
2197     checked_free(ti->graphics_set);
2198     checked_free(ti->sounds_set);
2199     checked_free(ti->music_set);
2200
2201     checked_free(ti->graphics_path);
2202     checked_free(ti->sounds_path);
2203     checked_free(ti->music_path);
2204
2205     checked_free(ti->level_filename);
2206     checked_free(ti->level_filetype);
2207   }
2208
2209   checked_free(ti);
2210 }
2211
2212 void setSetupInfo(struct TokenInfo *token_info,
2213                   int token_nr, char *token_value)
2214 {
2215   int token_type = token_info[token_nr].type;
2216   void *setup_value = token_info[token_nr].value;
2217
2218   if (token_value == NULL)
2219     return;
2220
2221   /* set setup field to corresponding token value */
2222   switch (token_type)
2223   {
2224     case TYPE_BOOLEAN:
2225     case TYPE_SWITCH:
2226       *(boolean *)setup_value = get_boolean_from_string(token_value);
2227       break;
2228
2229     case TYPE_KEY:
2230       *(Key *)setup_value = getKeyFromKeyName(token_value);
2231       break;
2232
2233     case TYPE_KEY_X11:
2234       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2235       break;
2236
2237     case TYPE_INTEGER:
2238       *(int *)setup_value = get_integer_from_string(token_value);
2239       break;
2240
2241     case TYPE_STRING:
2242       checked_free(*(char **)setup_value);
2243       *(char **)setup_value = getStringCopy(token_value);
2244       break;
2245
2246     default:
2247       break;
2248   }
2249 }
2250
2251 static int compareTreeInfoEntries(const void *object1, const void *object2)
2252 {
2253   const TreeInfo *entry1 = *((TreeInfo **)object1);
2254   const TreeInfo *entry2 = *((TreeInfo **)object2);
2255   int class_sorting1, class_sorting2;
2256   int compare_result;
2257
2258   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2259   {
2260     class_sorting1 = LEVELSORTING(entry1);
2261     class_sorting2 = LEVELSORTING(entry2);
2262   }
2263   else
2264   {
2265     class_sorting1 = ARTWORKSORTING(entry1);
2266     class_sorting2 = ARTWORKSORTING(entry2);
2267   }
2268
2269   if (entry1->parent_link || entry2->parent_link)
2270     compare_result = (entry1->parent_link ? -1 : +1);
2271   else if (entry1->sort_priority == entry2->sort_priority)
2272   {
2273     char *name1 = getStringToLower(entry1->name_sorting);
2274     char *name2 = getStringToLower(entry2->name_sorting);
2275
2276     compare_result = strcmp(name1, name2);
2277
2278     free(name1);
2279     free(name2);
2280   }
2281   else if (class_sorting1 == class_sorting2)
2282     compare_result = entry1->sort_priority - entry2->sort_priority;
2283   else
2284     compare_result = class_sorting1 - class_sorting2;
2285
2286   return compare_result;
2287 }
2288
2289 static void createParentTreeInfoNode(TreeInfo *node_parent)
2290 {
2291   TreeInfo *ti_new;
2292
2293   if (node_parent == NULL)
2294     return;
2295
2296   ti_new = newTreeInfo();
2297   setTreeInfoToDefaults(ti_new, node_parent->type);
2298
2299   ti_new->node_parent = node_parent;
2300   ti_new->parent_link = TRUE;
2301
2302   setString(&ti_new->identifier, node_parent->identifier);
2303   setString(&ti_new->name, ".. (parent directory)");
2304   setString(&ti_new->name_sorting, ti_new->name);
2305
2306   setString(&ti_new->subdir, "..");
2307   setString(&ti_new->fullpath, node_parent->fullpath);
2308
2309   ti_new->sort_priority = node_parent->sort_priority;
2310   ti_new->latest_engine = node_parent->latest_engine;
2311
2312   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2313
2314   pushTreeInfo(&node_parent->node_group, ti_new);
2315 }
2316
2317
2318 /* -------------------------------------------------------------------------- */
2319 /* functions for handling level and custom artwork info cache                 */
2320 /* -------------------------------------------------------------------------- */
2321
2322 static void LoadArtworkInfoCache()
2323 {
2324   InitCacheDirectory();
2325
2326   if (artworkinfo_cache_old == NULL)
2327   {
2328     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2329
2330     /* try to load artwork info hash from already existing cache file */
2331     artworkinfo_cache_old = loadSetupFileHash(filename);
2332
2333     /* if no artwork info cache file was found, start with empty hash */
2334     if (artworkinfo_cache_old == NULL)
2335       artworkinfo_cache_old = newSetupFileHash();
2336
2337     free(filename);
2338   }
2339
2340   if (artworkinfo_cache_new == NULL)
2341     artworkinfo_cache_new = newSetupFileHash();
2342 }
2343
2344 static void SaveArtworkInfoCache()
2345 {
2346   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2347
2348   InitCacheDirectory();
2349
2350   saveSetupFileHash(artworkinfo_cache_new, filename);
2351
2352   free(filename);
2353 }
2354
2355 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2356 {
2357   static char *prefix = NULL;
2358
2359   checked_free(prefix);
2360
2361   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2362
2363   return prefix;
2364 }
2365
2366 /* (identical to above function, but separate string buffer needed -- nasty) */
2367 static char *getCacheToken(char *prefix, char *suffix)
2368 {
2369   static char *token = NULL;
2370
2371   checked_free(token);
2372
2373   token = getStringCat2WithSeparator(prefix, suffix, ".");
2374
2375   return token;
2376 }
2377
2378 static char *getFileTimestamp(char *filename)
2379 {
2380   struct stat file_status;
2381
2382   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2383     return getStringCopy(i_to_a(0));
2384
2385   return getStringCopy(i_to_a(file_status.st_mtime));
2386 }
2387
2388 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2389 {
2390   struct stat file_status;
2391
2392   if (timestamp_string == NULL)
2393     return TRUE;
2394
2395   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2396     return TRUE;
2397
2398   return (file_status.st_mtime != atoi(timestamp_string));
2399 }
2400
2401 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2402 {
2403   char *identifier = level_node->subdir;
2404   char *type_string = ARTWORK_DIRECTORY(type);
2405   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2406   char *token_main = getCacheToken(token_prefix, "CACHED");
2407   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2408   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2409   TreeInfo *artwork_info = NULL;
2410
2411   if (!use_artworkinfo_cache)
2412     return NULL;
2413
2414   if (cached)
2415   {
2416     int i;
2417
2418     artwork_info = newTreeInfo();
2419     setTreeInfoToDefaults(artwork_info, type);
2420
2421     /* set all structure fields according to the token/value pairs */
2422     ldi = *artwork_info;
2423     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2424     {
2425       char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2426       char *value = getHashEntry(artworkinfo_cache_old, token);
2427
2428       setSetupInfo(artworkinfo_tokens, i, value);
2429
2430       /* check if cache entry for this item is invalid or incomplete */
2431       if (value == NULL)
2432       {
2433 #if 1
2434         Error(ERR_WARN, "cache entry '%s' invalid", token);
2435 #endif
2436
2437         cached = FALSE;
2438       }
2439     }
2440
2441     *artwork_info = ldi;
2442   }
2443
2444   if (cached)
2445   {
2446     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2447                                         LEVELINFO_FILENAME);
2448     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2449                                           ARTWORKINFO_FILENAME(type));
2450
2451     /* check if corresponding "levelinfo.conf" file has changed */
2452     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2453     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2454
2455     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2456       cached = FALSE;
2457
2458     /* check if corresponding "<artworkinfo>.conf" file has changed */
2459     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2460     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2461
2462     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2463       cached = FALSE;
2464
2465 #if 0
2466     if (!cached)
2467       printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2468 #endif
2469
2470     checked_free(filename_levelinfo);
2471     checked_free(filename_artworkinfo);
2472   }
2473
2474   if (!cached && artwork_info != NULL)
2475   {
2476     freeTreeInfo(artwork_info);
2477
2478     return NULL;
2479   }
2480
2481   return artwork_info;
2482 }
2483
2484 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2485                                      LevelDirTree *level_node, int type)
2486 {
2487   char *identifier = level_node->subdir;
2488   char *type_string = ARTWORK_DIRECTORY(type);
2489   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2490   char *token_main = getCacheToken(token_prefix, "CACHED");
2491   boolean set_cache_timestamps = TRUE;
2492   int i;
2493
2494   setHashEntry(artworkinfo_cache_new, token_main, "true");
2495
2496   if (set_cache_timestamps)
2497   {
2498     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2499                                         LEVELINFO_FILENAME);
2500     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2501                                           ARTWORKINFO_FILENAME(type));
2502     char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2503     char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2504
2505     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2506     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2507
2508     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2509     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2510
2511     checked_free(filename_levelinfo);
2512     checked_free(filename_artworkinfo);
2513     checked_free(timestamp_levelinfo);
2514     checked_free(timestamp_artworkinfo);
2515   }
2516
2517   ldi = *artwork_info;
2518   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2519   {
2520     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2521     char *value = getSetupValue(artworkinfo_tokens[i].type,
2522                                 artworkinfo_tokens[i].value);
2523     if (value != NULL)
2524       setHashEntry(artworkinfo_cache_new, token, value);
2525   }
2526 }
2527
2528
2529 /* -------------------------------------------------------------------------- */
2530 /* functions for loading level info and custom artwork info                   */
2531 /* -------------------------------------------------------------------------- */
2532
2533 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2534 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2535
2536 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2537                                           TreeInfo *node_parent,
2538                                           char *level_directory,
2539                                           char *directory_name)
2540 {
2541   static unsigned long progress_delay = 0;
2542   unsigned long progress_delay_value = 100;     /* (in milliseconds) */
2543   char *directory_path = getPath2(level_directory, directory_name);
2544   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2545   SetupFileHash *setup_file_hash;
2546   LevelDirTree *leveldir_new = NULL;
2547   int i;
2548
2549   /* unless debugging, silently ignore directories without "levelinfo.conf" */
2550   if (!options.debug && !fileExists(filename))
2551   {
2552     free(directory_path);
2553     free(filename);
2554
2555     return FALSE;
2556   }
2557
2558   setup_file_hash = loadSetupFileHash(filename);
2559
2560   if (setup_file_hash == NULL)
2561   {
2562     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2563
2564     free(directory_path);
2565     free(filename);
2566
2567     return FALSE;
2568   }
2569
2570   leveldir_new = newTreeInfo();
2571
2572   if (node_parent)
2573     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2574   else
2575     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2576
2577   leveldir_new->subdir = getStringCopy(directory_name);
2578
2579   checkSetupFileHashIdentifier(setup_file_hash, filename,
2580                                getCookie("LEVELINFO"));
2581
2582   /* set all structure fields according to the token/value pairs */
2583   ldi = *leveldir_new;
2584   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2585     setSetupInfo(levelinfo_tokens, i,
2586                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2587   *leveldir_new = ldi;
2588
2589   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2590     setString(&leveldir_new->name, leveldir_new->subdir);
2591
2592   if (leveldir_new->identifier == NULL)
2593     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2594
2595   if (leveldir_new->name_sorting == NULL)
2596     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2597
2598   if (node_parent == NULL)              /* top level group */
2599   {
2600     leveldir_new->basepath = getStringCopy(level_directory);
2601     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2602   }
2603   else                                  /* sub level group */
2604   {
2605     leveldir_new->basepath = getStringCopy(node_parent->basepath);
2606     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2607   }
2608
2609 #if 0
2610   if (leveldir_new->levels < 1)
2611     leveldir_new->levels = 1;
2612 #endif
2613
2614   leveldir_new->last_level =
2615     leveldir_new->first_level + leveldir_new->levels - 1;
2616
2617   leveldir_new->in_user_dir =
2618     (!strEqual(leveldir_new->basepath, options.level_directory));
2619
2620   /* adjust some settings if user's private level directory was detected */
2621   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2622       leveldir_new->in_user_dir &&
2623       (strEqual(leveldir_new->subdir, getLoginName()) ||
2624        strEqual(leveldir_new->name,   getLoginName()) ||
2625        strEqual(leveldir_new->author, getRealName())))
2626   {
2627     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2628     leveldir_new->readonly = FALSE;
2629   }
2630
2631   leveldir_new->user_defined =
2632     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2633
2634   leveldir_new->color = LEVELCOLOR(leveldir_new);
2635
2636   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2637
2638   leveldir_new->handicap_level =        /* set handicap to default value */
2639     (leveldir_new->user_defined || !leveldir_new->handicap ?
2640      leveldir_new->last_level : leveldir_new->first_level);
2641
2642 #if 1
2643   if (leveldir_new->level_group ||
2644       DelayReached(&progress_delay, progress_delay_value))
2645     DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2646 #else
2647   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2648 #endif
2649
2650 #if 0
2651   /* !!! don't skip sets without levels (else artwork base sets are missing) */
2652 #if 1
2653   if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2654   {
2655     /* skip level sets without levels (which are probably artwork base sets) */
2656
2657     freeSetupFileHash(setup_file_hash);
2658     free(directory_path);
2659     free(filename);
2660
2661     return FALSE;
2662   }
2663 #endif
2664 #endif
2665
2666   pushTreeInfo(node_first, leveldir_new);
2667
2668   freeSetupFileHash(setup_file_hash);
2669
2670   if (leveldir_new->level_group)
2671   {
2672     /* create node to link back to current level directory */
2673     createParentTreeInfoNode(leveldir_new);
2674
2675     /* recursively step into sub-directory and look for more level series */
2676     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2677                               leveldir_new, directory_path);
2678   }
2679
2680   free(directory_path);
2681   free(filename);
2682
2683   return TRUE;
2684 }
2685
2686 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2687                                       TreeInfo *node_parent,
2688                                       char *level_directory)
2689 {
2690   DIR *dir;
2691   struct dirent *dir_entry;
2692   boolean valid_entry_found = FALSE;
2693
2694   if ((dir = opendir(level_directory)) == NULL)
2695   {
2696     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2697     return;
2698   }
2699
2700   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2701   {
2702     struct stat file_status;
2703     char *directory_name = dir_entry->d_name;
2704     char *directory_path = getPath2(level_directory, directory_name);
2705
2706     /* skip entries for current and parent directory */
2707     if (strEqual(directory_name, ".") ||
2708         strEqual(directory_name, ".."))
2709     {
2710       free(directory_path);
2711       continue;
2712     }
2713
2714     /* find out if directory entry is itself a directory */
2715     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2716         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2717     {
2718       free(directory_path);
2719       continue;
2720     }
2721
2722     free(directory_path);
2723
2724     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2725         strEqual(directory_name, SOUNDS_DIRECTORY) ||
2726         strEqual(directory_name, MUSIC_DIRECTORY))
2727       continue;
2728
2729     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2730                                                     level_directory,
2731                                                     directory_name);
2732   }
2733
2734   closedir(dir);
2735
2736   /* special case: top level directory may directly contain "levelinfo.conf" */
2737   if (node_parent == NULL && !valid_entry_found)
2738   {
2739     /* check if this directory directly contains a file "levelinfo.conf" */
2740     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2741                                                     level_directory, ".");
2742   }
2743
2744   if (!valid_entry_found)
2745     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2746           level_directory);
2747 }
2748
2749 boolean AdjustGraphicsForEMC()
2750 {
2751   boolean settings_changed = FALSE;
2752
2753   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2754   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2755
2756   return settings_changed;
2757 }
2758
2759 void LoadLevelInfo()
2760 {
2761   InitUserLevelDirectory(getLoginName());
2762
2763   DrawInitText("Loading level series", 120, FC_GREEN);
2764
2765   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2766   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2767
2768   /* after loading all level set information, clone the level directory tree
2769      and remove all level sets without levels (these may still contain artwork
2770      to be offered in the setup menu as "custom artwork", and are therefore
2771      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2772   leveldir_first_all = leveldir_first;
2773   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2774
2775   AdjustGraphicsForEMC();
2776
2777   /* before sorting, the first entries will be from the user directory */
2778   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2779
2780   if (leveldir_first == NULL)
2781     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2782
2783   sortTreeInfo(&leveldir_first);
2784
2785 #if 0
2786   dumpTreeInfo(leveldir_first, 0);
2787 #endif
2788 }
2789
2790 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2791                                               TreeInfo *node_parent,
2792                                               char *base_directory,
2793                                               char *directory_name, int type)
2794 {
2795   char *directory_path = getPath2(base_directory, directory_name);
2796   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2797   SetupFileHash *setup_file_hash = NULL;
2798   TreeInfo *artwork_new = NULL;
2799   int i;
2800
2801   if (fileExists(filename))
2802     setup_file_hash = loadSetupFileHash(filename);
2803
2804   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2805   {
2806     DIR *dir;
2807     struct dirent *dir_entry;
2808     boolean valid_file_found = FALSE;
2809
2810     if ((dir = opendir(directory_path)) != NULL)
2811     {
2812       while ((dir_entry = readdir(dir)) != NULL)
2813       {
2814         char *entry_name = dir_entry->d_name;
2815
2816         if (FileIsArtworkType(entry_name, type))
2817         {
2818           valid_file_found = TRUE;
2819           break;
2820         }
2821       }
2822
2823       closedir(dir);
2824     }
2825
2826     if (!valid_file_found)
2827     {
2828       if (!strEqual(directory_name, "."))
2829         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2830
2831       free(directory_path);
2832       free(filename);
2833
2834       return FALSE;
2835     }
2836   }
2837
2838   artwork_new = newTreeInfo();
2839
2840   if (node_parent)
2841     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2842   else
2843     setTreeInfoToDefaults(artwork_new, type);
2844
2845   artwork_new->subdir = getStringCopy(directory_name);
2846
2847   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2848   {
2849 #if 0
2850     checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2851 #endif
2852
2853     /* set all structure fields according to the token/value pairs */
2854     ldi = *artwork_new;
2855     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2856       setSetupInfo(levelinfo_tokens, i,
2857                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2858     *artwork_new = ldi;
2859
2860     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2861       setString(&artwork_new->name, artwork_new->subdir);
2862
2863     if (artwork_new->identifier == NULL)
2864       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2865
2866     if (artwork_new->name_sorting == NULL)
2867       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2868   }
2869
2870   if (node_parent == NULL)              /* top level group */
2871   {
2872     artwork_new->basepath = getStringCopy(base_directory);
2873     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2874   }
2875   else                                  /* sub level group */
2876   {
2877     artwork_new->basepath = getStringCopy(node_parent->basepath);
2878     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2879   }
2880
2881   artwork_new->in_user_dir =
2882     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2883
2884   /* (may use ".sort_priority" from "setup_file_hash" above) */
2885   artwork_new->color = ARTWORKCOLOR(artwork_new);
2886
2887   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2888
2889   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2890   {
2891     if (strEqual(artwork_new->subdir, "."))
2892     {
2893       if (artwork_new->user_defined)
2894       {
2895         setString(&artwork_new->identifier, "private");
2896         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2897       }
2898       else
2899       {
2900         setString(&artwork_new->identifier, "classic");
2901         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2902       }
2903
2904       /* set to new values after changing ".sort_priority" */
2905       artwork_new->color = ARTWORKCOLOR(artwork_new);
2906
2907       setString(&artwork_new->class_desc,
2908                 getLevelClassDescription(artwork_new));
2909     }
2910     else
2911     {
2912       setString(&artwork_new->identifier, artwork_new->subdir);
2913     }
2914
2915     setString(&artwork_new->name, artwork_new->identifier);
2916     setString(&artwork_new->name_sorting, artwork_new->name);
2917   }
2918
2919 #if 0
2920   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2921 #endif
2922
2923   pushTreeInfo(node_first, artwork_new);
2924
2925   freeSetupFileHash(setup_file_hash);
2926
2927   free(directory_path);
2928   free(filename);
2929
2930   return TRUE;
2931 }
2932
2933 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2934                                           TreeInfo *node_parent,
2935                                           char *base_directory, int type)
2936 {
2937   DIR *dir;
2938   struct dirent *dir_entry;
2939   boolean valid_entry_found = FALSE;
2940
2941   if ((dir = opendir(base_directory)) == NULL)
2942   {
2943     /* display error if directory is main "options.graphics_directory" etc. */
2944     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2945       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2946
2947     return;
2948   }
2949
2950   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2951   {
2952     struct stat file_status;
2953     char *directory_name = dir_entry->d_name;
2954     char *directory_path = getPath2(base_directory, directory_name);
2955
2956     /* skip directory entries for current and parent directory */
2957     if (strEqual(directory_name, ".") ||
2958         strEqual(directory_name, ".."))
2959     {
2960       free(directory_path);
2961       continue;
2962     }
2963
2964     /* skip directory entries which are not a directory or are not accessible */
2965     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2966         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2967     {
2968       free(directory_path);
2969       continue;
2970     }
2971
2972     free(directory_path);
2973
2974     /* check if this directory contains artwork with or without config file */
2975     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2976                                                         base_directory,
2977                                                         directory_name, type);
2978   }
2979
2980   closedir(dir);
2981
2982   /* check if this directory directly contains artwork itself */
2983   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2984                                                       base_directory, ".",
2985                                                       type);
2986   if (!valid_entry_found)
2987     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2988           base_directory);
2989 }
2990
2991 static TreeInfo *getDummyArtworkInfo(int type)
2992 {
2993   /* this is only needed when there is completely no artwork available */
2994   TreeInfo *artwork_new = newTreeInfo();
2995
2996   setTreeInfoToDefaults(artwork_new, type);
2997
2998   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
2999   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3000   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3001
3002   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3003   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3004   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3005
3006   return artwork_new;
3007 }
3008
3009 void LoadArtworkInfo()
3010 {
3011   LoadArtworkInfoCache();
3012
3013   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3014
3015   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3016                                 options.graphics_directory,
3017                                 TREE_TYPE_GRAPHICS_DIR);
3018   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3019                                 getUserGraphicsDir(),
3020                                 TREE_TYPE_GRAPHICS_DIR);
3021
3022   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3023                                 options.sounds_directory,
3024                                 TREE_TYPE_SOUNDS_DIR);
3025   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3026                                 getUserSoundsDir(),
3027                                 TREE_TYPE_SOUNDS_DIR);
3028
3029   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3030                                 options.music_directory,
3031                                 TREE_TYPE_MUSIC_DIR);
3032   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3033                                 getUserMusicDir(),
3034                                 TREE_TYPE_MUSIC_DIR);
3035
3036   if (artwork.gfx_first == NULL)
3037     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3038   if (artwork.snd_first == NULL)
3039     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3040   if (artwork.mus_first == NULL)
3041     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3042
3043   /* before sorting, the first entries will be from the user directory */
3044   artwork.gfx_current =
3045     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3046   if (artwork.gfx_current == NULL)
3047     artwork.gfx_current =
3048       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3049   if (artwork.gfx_current == NULL)
3050     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3051
3052   artwork.snd_current =
3053     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3054   if (artwork.snd_current == NULL)
3055     artwork.snd_current =
3056       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3057   if (artwork.snd_current == NULL)
3058     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3059
3060   artwork.mus_current =
3061     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3062   if (artwork.mus_current == NULL)
3063     artwork.mus_current =
3064       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3065   if (artwork.mus_current == NULL)
3066     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3067
3068   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3069   artwork.snd_current_identifier = artwork.snd_current->identifier;
3070   artwork.mus_current_identifier = artwork.mus_current->identifier;
3071
3072 #if 0
3073   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3074   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3075   printf("music set == %s\n\n", artwork.mus_current_identifier);
3076 #endif
3077
3078   sortTreeInfo(&artwork.gfx_first);
3079   sortTreeInfo(&artwork.snd_first);
3080   sortTreeInfo(&artwork.mus_first);
3081
3082 #if 0
3083   dumpTreeInfo(artwork.gfx_first, 0);
3084   dumpTreeInfo(artwork.snd_first, 0);
3085   dumpTreeInfo(artwork.mus_first, 0);
3086 #endif
3087 }
3088
3089 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3090                                   LevelDirTree *level_node)
3091 {
3092   static unsigned long progress_delay = 0;
3093   unsigned long progress_delay_value = 100;     /* (in milliseconds) */
3094   int type = (*artwork_node)->type;
3095
3096   /* recursively check all level directories for artwork sub-directories */
3097
3098   while (level_node)
3099   {
3100     /* check all tree entries for artwork, but skip parent link entries */
3101     if (!level_node->parent_link)
3102     {
3103       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3104       boolean cached = (artwork_new != NULL);
3105
3106       if (cached)
3107       {
3108         pushTreeInfo(artwork_node, artwork_new);
3109       }
3110       else
3111       {
3112         TreeInfo *topnode_last = *artwork_node;
3113         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3114                               ARTWORK_DIRECTORY(type));
3115
3116         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3117
3118         if (topnode_last != *artwork_node)      /* check for newly added node */
3119         {
3120           artwork_new = *artwork_node;
3121
3122           setString(&artwork_new->identifier,   level_node->subdir);
3123           setString(&artwork_new->name,         level_node->name);
3124           setString(&artwork_new->name_sorting, level_node->name_sorting);
3125
3126           artwork_new->sort_priority = level_node->sort_priority;
3127           artwork_new->color = LEVELCOLOR(artwork_new);
3128         }
3129
3130         free(path);
3131       }
3132
3133       /* insert artwork info (from old cache or filesystem) into new cache */
3134       if (artwork_new != NULL)
3135         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3136     }
3137
3138 #if 1
3139     if (level_node->level_group ||
3140         DelayReached(&progress_delay, progress_delay_value))
3141       DrawInitText(level_node->name, 150, FC_YELLOW);
3142 #endif
3143
3144     if (level_node->node_group != NULL)
3145       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3146
3147     level_node = level_node->next;
3148   }
3149 }
3150
3151 void LoadLevelArtworkInfo()
3152 {
3153   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3154
3155   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3156   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3157   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3158
3159   SaveArtworkInfoCache();
3160
3161   /* needed for reloading level artwork not known at ealier stage */
3162
3163   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3164   {
3165     artwork.gfx_current =
3166       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3167     if (artwork.gfx_current == NULL)
3168       artwork.gfx_current =
3169         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3170     if (artwork.gfx_current == NULL)
3171       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3172   }
3173
3174   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3175   {
3176     artwork.snd_current =
3177       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3178     if (artwork.snd_current == NULL)
3179       artwork.snd_current =
3180         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3181     if (artwork.snd_current == NULL)
3182       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3183   }
3184
3185   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3186   {
3187     artwork.mus_current =
3188       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3189     if (artwork.mus_current == NULL)
3190       artwork.mus_current =
3191         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3192     if (artwork.mus_current == NULL)
3193       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3194   }
3195
3196   sortTreeInfo(&artwork.gfx_first);
3197   sortTreeInfo(&artwork.snd_first);
3198   sortTreeInfo(&artwork.mus_first);
3199
3200 #if 0
3201   dumpTreeInfo(artwork.gfx_first, 0);
3202   dumpTreeInfo(artwork.snd_first, 0);
3203   dumpTreeInfo(artwork.mus_first, 0);
3204 #endif
3205 }
3206
3207 static void SaveUserLevelInfo()
3208 {
3209   LevelDirTree *level_info;
3210   char *filename;
3211   FILE *file;
3212   int i;
3213
3214   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3215
3216   if (!(file = fopen(filename, MODE_WRITE)))
3217   {
3218     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3219     free(filename);
3220     return;
3221   }
3222
3223   level_info = newTreeInfo();
3224
3225   /* always start with reliable default values */
3226   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3227
3228   setString(&level_info->name, getLoginName());
3229   setString(&level_info->author, getRealName());
3230   level_info->levels = 100;
3231   level_info->first_level = 1;
3232
3233   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3234
3235   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3236                                                  getCookie("LEVELINFO")));
3237
3238   ldi = *level_info;
3239   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3240   {
3241     if (i == LEVELINFO_TOKEN_NAME ||
3242         i == LEVELINFO_TOKEN_AUTHOR ||
3243         i == LEVELINFO_TOKEN_LEVELS ||
3244         i == LEVELINFO_TOKEN_FIRST_LEVEL)
3245       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3246
3247     /* just to make things nicer :) */
3248     if (i == LEVELINFO_TOKEN_AUTHOR)
3249       fprintf(file, "\n");      
3250   }
3251
3252   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3253
3254   fclose(file);
3255
3256   SetFilePermissions(filename, PERMS_PRIVATE);
3257
3258   freeTreeInfo(level_info);
3259   free(filename);
3260 }
3261
3262 char *getSetupValue(int type, void *value)
3263 {
3264   static char value_string[MAX_LINE_LEN];
3265
3266   if (value == NULL)
3267     return NULL;
3268
3269   switch (type)
3270   {
3271     case TYPE_BOOLEAN:
3272       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3273       break;
3274
3275     case TYPE_SWITCH:
3276       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3277       break;
3278
3279     case TYPE_YES_NO:
3280       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3281       break;
3282
3283     case TYPE_ECS_AGA:
3284       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3285       break;
3286
3287     case TYPE_KEY:
3288       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3289       break;
3290
3291     case TYPE_KEY_X11:
3292       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3293       break;
3294
3295     case TYPE_INTEGER:
3296       sprintf(value_string, "%d", *(int *)value);
3297       break;
3298
3299     case TYPE_STRING:
3300       if (*(char **)value == NULL)
3301         return NULL;
3302
3303       strcpy(value_string, *(char **)value);
3304       break;
3305
3306     default:
3307       value_string[0] = '\0';
3308       break;
3309   }
3310
3311   if (type & TYPE_GHOSTED)
3312     strcpy(value_string, "n/a");
3313
3314   return value_string;
3315 }
3316
3317 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3318 {
3319   int i;
3320   char *line;
3321   static char token_string[MAX_LINE_LEN];
3322   int token_type = token_info[token_nr].type;
3323   void *setup_value = token_info[token_nr].value;
3324   char *token_text = token_info[token_nr].text;
3325   char *value_string = getSetupValue(token_type, setup_value);
3326
3327   /* build complete token string */
3328   sprintf(token_string, "%s%s", prefix, token_text);
3329
3330   /* build setup entry line */
3331   line = getFormattedSetupEntry(token_string, value_string);
3332
3333   if (token_type == TYPE_KEY_X11)
3334   {
3335     Key key = *(Key *)setup_value;
3336     char *keyname = getKeyNameFromKey(key);
3337
3338     /* add comment, if useful */
3339     if (!strEqual(keyname, "(undefined)") &&
3340         !strEqual(keyname, "(unknown)"))
3341     {
3342       /* add at least one whitespace */
3343       strcat(line, " ");
3344       for (i = strlen(line); i < token_comment_position; i++)
3345         strcat(line, " ");
3346
3347       strcat(line, "# ");
3348       strcat(line, keyname);
3349     }
3350   }
3351
3352   return line;
3353 }
3354
3355 void LoadLevelSetup_LastSeries()
3356 {
3357   /* ----------------------------------------------------------------------- */
3358   /* ~/.<program>/levelsetup.conf                                            */
3359   /* ----------------------------------------------------------------------- */
3360
3361   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3362   SetupFileHash *level_setup_hash = NULL;
3363
3364   /* always start with reliable default values */
3365   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3366
3367   if ((level_setup_hash = loadSetupFileHash(filename)))
3368   {
3369     char *last_level_series =
3370       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3371
3372     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3373                                                  last_level_series);
3374     if (leveldir_current == NULL)
3375       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3376
3377     checkSetupFileHashIdentifier(level_setup_hash, filename,
3378                                  getCookie("LEVELSETUP"));
3379
3380     freeSetupFileHash(level_setup_hash);
3381   }
3382   else
3383     Error(ERR_WARN, "using default setup values");
3384
3385   free(filename);
3386 }
3387
3388 void SaveLevelSetup_LastSeries()
3389 {
3390   /* ----------------------------------------------------------------------- */
3391   /* ~/.<program>/levelsetup.conf                                            */
3392   /* ----------------------------------------------------------------------- */
3393
3394   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3395   char *level_subdir = leveldir_current->subdir;
3396   FILE *file;
3397
3398   InitUserDataDirectory();
3399
3400   if (!(file = fopen(filename, MODE_WRITE)))
3401   {
3402     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3403     free(filename);
3404     return;
3405   }
3406
3407   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3408                                                  getCookie("LEVELSETUP")));
3409   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3410                                                level_subdir));
3411
3412   fclose(file);
3413
3414   SetFilePermissions(filename, PERMS_PRIVATE);
3415
3416   free(filename);
3417 }
3418
3419 static void checkSeriesInfo()
3420 {
3421   static char *level_directory = NULL;
3422   DIR *dir;
3423   struct dirent *dir_entry;
3424
3425   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3426
3427   level_directory = getPath2((leveldir_current->in_user_dir ?
3428                               getUserLevelDir(NULL) :
3429                               options.level_directory),
3430                              leveldir_current->fullpath);
3431
3432   if ((dir = opendir(level_directory)) == NULL)
3433   {
3434     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3435     return;
3436   }
3437
3438   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
3439   {
3440     if (strlen(dir_entry->d_name) > 4 &&
3441         dir_entry->d_name[3] == '.' &&
3442         strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3443     {
3444       char levelnum_str[4];
3445       int levelnum_value;
3446
3447       strncpy(levelnum_str, dir_entry->d_name, 3);
3448       levelnum_str[3] = '\0';
3449
3450       levelnum_value = atoi(levelnum_str);
3451
3452 #if 0
3453       if (levelnum_value < leveldir_current->first_level)
3454       {
3455         Error(ERR_WARN, "additional level %d found", levelnum_value);
3456         leveldir_current->first_level = levelnum_value;
3457       }
3458       else if (levelnum_value > leveldir_current->last_level)
3459       {
3460         Error(ERR_WARN, "additional level %d found", levelnum_value);
3461         leveldir_current->last_level = levelnum_value;
3462       }
3463 #endif
3464     }
3465   }
3466
3467   closedir(dir);
3468 }
3469
3470 void LoadLevelSetup_SeriesInfo()
3471 {
3472   char *filename;
3473   SetupFileHash *level_setup_hash = NULL;
3474   char *level_subdir = leveldir_current->subdir;
3475
3476   /* always start with reliable default values */
3477   level_nr = leveldir_current->first_level;
3478
3479   checkSeriesInfo(leveldir_current);
3480
3481   /* ----------------------------------------------------------------------- */
3482   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
3483   /* ----------------------------------------------------------------------- */
3484
3485   level_subdir = leveldir_current->subdir;
3486
3487   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3488
3489   if ((level_setup_hash = loadSetupFileHash(filename)))
3490   {
3491     char *token_value;
3492
3493     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3494
3495     if (token_value)
3496     {
3497       level_nr = atoi(token_value);
3498
3499       if (level_nr < leveldir_current->first_level)
3500         level_nr = leveldir_current->first_level;
3501       if (level_nr > leveldir_current->last_level)
3502         level_nr = leveldir_current->last_level;
3503     }
3504
3505     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3506
3507     if (token_value)
3508     {
3509       int level_nr = atoi(token_value);
3510
3511       if (level_nr < leveldir_current->first_level)
3512         level_nr = leveldir_current->first_level;
3513       if (level_nr > leveldir_current->last_level + 1)
3514         level_nr = leveldir_current->last_level;
3515
3516       if (leveldir_current->user_defined || !leveldir_current->handicap)
3517         level_nr = leveldir_current->last_level;
3518
3519       leveldir_current->handicap_level = level_nr;
3520     }
3521
3522     checkSetupFileHashIdentifier(level_setup_hash, filename,
3523                                  getCookie("LEVELSETUP"));
3524
3525     freeSetupFileHash(level_setup_hash);
3526   }
3527   else
3528     Error(ERR_WARN, "using default setup values");
3529
3530   free(filename);
3531 }
3532
3533 void SaveLevelSetup_SeriesInfo()
3534 {
3535   char *filename;
3536   char *level_subdir = leveldir_current->subdir;
3537   char *level_nr_str = int2str(level_nr, 0);
3538   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3539   FILE *file;
3540
3541   /* ----------------------------------------------------------------------- */
3542   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
3543   /* ----------------------------------------------------------------------- */
3544
3545   InitLevelSetupDirectory(level_subdir);
3546
3547   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3548
3549   if (!(file = fopen(filename, MODE_WRITE)))
3550   {
3551     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3552     free(filename);
3553     return;
3554   }
3555
3556   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3557                                                  getCookie("LEVELSETUP")));
3558   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3559                                                level_nr_str));
3560   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3561                                                handicap_level_str));
3562
3563   fclose(file);
3564
3565   SetFilePermissions(filename, PERMS_PRIVATE);
3566
3567   free(filename);
3568 }