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