rnd-20070104-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         printf("::: - WARNING: cache entry '%s' invalid\n", 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   char *directory_path = getPath2(level_directory, directory_name);
2456   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2457   SetupFileHash *setup_file_hash;
2458   LevelDirTree *leveldir_new = NULL;
2459   int i;
2460
2461   /* unless debugging, silently ignore directories without "levelinfo.conf" */
2462   if (!options.debug && !fileExists(filename))
2463   {
2464     free(directory_path);
2465     free(filename);
2466
2467     return FALSE;
2468   }
2469
2470   setup_file_hash = loadSetupFileHash(filename);
2471
2472   if (setup_file_hash == NULL)
2473   {
2474     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2475
2476     free(directory_path);
2477     free(filename);
2478
2479     return FALSE;
2480   }
2481
2482   leveldir_new = newTreeInfo();
2483
2484   if (node_parent)
2485     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2486   else
2487     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2488
2489   leveldir_new->subdir = getStringCopy(directory_name);
2490
2491   checkSetupFileHashIdentifier(setup_file_hash, filename,
2492                                getCookie("LEVELINFO"));
2493
2494   /* set all structure fields according to the token/value pairs */
2495   ldi = *leveldir_new;
2496   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2497     setSetupInfo(levelinfo_tokens, i,
2498                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2499   *leveldir_new = ldi;
2500
2501   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2502     setString(&leveldir_new->name, leveldir_new->subdir);
2503
2504   if (leveldir_new->identifier == NULL)
2505     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2506
2507   if (leveldir_new->name_sorting == NULL)
2508     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2509
2510   if (node_parent == NULL)              /* top level group */
2511   {
2512     leveldir_new->basepath = getStringCopy(level_directory);
2513     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2514   }
2515   else                                  /* sub level group */
2516   {
2517     leveldir_new->basepath = getStringCopy(node_parent->basepath);
2518     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2519   }
2520
2521 #if 0
2522   if (leveldir_new->levels < 1)
2523     leveldir_new->levels = 1;
2524 #endif
2525
2526   leveldir_new->last_level =
2527     leveldir_new->first_level + leveldir_new->levels - 1;
2528
2529   leveldir_new->in_user_dir =
2530     (!strEqual(leveldir_new->basepath, options.level_directory));
2531
2532   /* adjust some settings if user's private level directory was detected */
2533   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2534       leveldir_new->in_user_dir &&
2535       (strEqual(leveldir_new->subdir, getLoginName()) ||
2536        strEqual(leveldir_new->name,   getLoginName()) ||
2537        strEqual(leveldir_new->author, getRealName())))
2538   {
2539     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2540     leveldir_new->readonly = FALSE;
2541   }
2542
2543   leveldir_new->user_defined =
2544     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2545
2546   leveldir_new->color = LEVELCOLOR(leveldir_new);
2547
2548   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2549
2550   leveldir_new->handicap_level =        /* set handicap to default value */
2551     (leveldir_new->user_defined || !leveldir_new->handicap ?
2552      leveldir_new->last_level : leveldir_new->first_level);
2553
2554 #if 1
2555   if (leveldir_new->level_group)
2556     DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2557 #else
2558   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2559 #endif
2560
2561 #if 0
2562   /* !!! don't skip sets without levels (else artwork base sets are missing) */
2563 #if 1
2564   if (leveldir_new->levels < 1 && !leveldir_new->level_group)
2565   {
2566     /* skip level sets without levels (which are probably artwork base sets) */
2567
2568     freeSetupFileHash(setup_file_hash);
2569     free(directory_path);
2570     free(filename);
2571
2572     return FALSE;
2573   }
2574 #endif
2575 #endif
2576
2577   pushTreeInfo(node_first, leveldir_new);
2578
2579   freeSetupFileHash(setup_file_hash);
2580
2581   if (leveldir_new->level_group)
2582   {
2583     /* create node to link back to current level directory */
2584     createParentTreeInfoNode(leveldir_new);
2585
2586     /* recursively step into sub-directory and look for more level series */
2587     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2588                               leveldir_new, directory_path);
2589   }
2590
2591   free(directory_path);
2592   free(filename);
2593
2594   return TRUE;
2595 }
2596
2597 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2598                                       TreeInfo *node_parent,
2599                                       char *level_directory)
2600 {
2601   DIR *dir;
2602   struct dirent *dir_entry;
2603   boolean valid_entry_found = FALSE;
2604
2605   if ((dir = opendir(level_directory)) == NULL)
2606   {
2607     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2608     return;
2609   }
2610
2611   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2612   {
2613     struct stat file_status;
2614     char *directory_name = dir_entry->d_name;
2615     char *directory_path = getPath2(level_directory, directory_name);
2616
2617     /* skip entries for current and parent directory */
2618     if (strEqual(directory_name, ".") ||
2619         strEqual(directory_name, ".."))
2620     {
2621       free(directory_path);
2622       continue;
2623     }
2624
2625     /* find out if directory entry is itself a directory */
2626     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2627         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2628     {
2629       free(directory_path);
2630       continue;
2631     }
2632
2633     free(directory_path);
2634
2635     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2636         strEqual(directory_name, SOUNDS_DIRECTORY) ||
2637         strEqual(directory_name, MUSIC_DIRECTORY))
2638       continue;
2639
2640     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2641                                                     level_directory,
2642                                                     directory_name);
2643   }
2644
2645   closedir(dir);
2646
2647   /* special case: top level directory may directly contain "levelinfo.conf" */
2648   if (node_parent == NULL && !valid_entry_found)
2649   {
2650     /* check if this directory directly contains a file "levelinfo.conf" */
2651     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2652                                                     level_directory, ".");
2653   }
2654
2655   if (!valid_entry_found)
2656     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2657           level_directory);
2658 }
2659
2660 boolean AdjustGraphicsForEMC()
2661 {
2662   boolean settings_changed = FALSE;
2663
2664   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2665   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2666
2667   return settings_changed;
2668 }
2669
2670 void LoadLevelInfo()
2671 {
2672   InitUserLevelDirectory(getLoginName());
2673
2674   DrawInitText("Loading level series", 120, FC_GREEN);
2675
2676   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2677   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2678
2679   /* after loading all level set information, clone the level directory tree
2680      and remove all level sets without levels (these may still contain artwork
2681      to be offered in the setup menu as "custom artwork", and are therefore
2682      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2683   leveldir_first_all = leveldir_first;
2684   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2685
2686   AdjustGraphicsForEMC();
2687
2688   /* before sorting, the first entries will be from the user directory */
2689   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2690
2691   if (leveldir_first == NULL)
2692     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2693
2694   sortTreeInfo(&leveldir_first);
2695
2696 #if 0
2697   dumpTreeInfo(leveldir_first, 0);
2698 #endif
2699 }
2700
2701 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2702                                               TreeInfo *node_parent,
2703                                               char *base_directory,
2704                                               char *directory_name, int type)
2705 {
2706   char *directory_path = getPath2(base_directory, directory_name);
2707   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2708   SetupFileHash *setup_file_hash = NULL;
2709   TreeInfo *artwork_new = NULL;
2710   int i;
2711
2712   if (fileExists(filename))
2713     setup_file_hash = loadSetupFileHash(filename);
2714
2715   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2716   {
2717     DIR *dir;
2718     struct dirent *dir_entry;
2719     boolean valid_file_found = FALSE;
2720
2721     if ((dir = opendir(directory_path)) != NULL)
2722     {
2723       while ((dir_entry = readdir(dir)) != NULL)
2724       {
2725         char *entry_name = dir_entry->d_name;
2726
2727         if (FileIsArtworkType(entry_name, type))
2728         {
2729           valid_file_found = TRUE;
2730           break;
2731         }
2732       }
2733
2734       closedir(dir);
2735     }
2736
2737     if (!valid_file_found)
2738     {
2739       if (!strEqual(directory_name, "."))
2740         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2741
2742       free(directory_path);
2743       free(filename);
2744
2745       return FALSE;
2746     }
2747   }
2748
2749   artwork_new = newTreeInfo();
2750
2751   if (node_parent)
2752     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2753   else
2754     setTreeInfoToDefaults(artwork_new, type);
2755
2756   artwork_new->subdir = getStringCopy(directory_name);
2757
2758   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2759   {
2760 #if 0
2761     checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
2762 #endif
2763
2764     /* set all structure fields according to the token/value pairs */
2765     ldi = *artwork_new;
2766     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2767       setSetupInfo(levelinfo_tokens, i,
2768                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2769     *artwork_new = ldi;
2770
2771     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
2772       setString(&artwork_new->name, artwork_new->subdir);
2773
2774     if (artwork_new->identifier == NULL)
2775       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2776
2777     if (artwork_new->name_sorting == NULL)
2778       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2779   }
2780
2781   if (node_parent == NULL)              /* top level group */
2782   {
2783     artwork_new->basepath = getStringCopy(base_directory);
2784     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2785   }
2786   else                                  /* sub level group */
2787   {
2788     artwork_new->basepath = getStringCopy(node_parent->basepath);
2789     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2790   }
2791
2792   artwork_new->in_user_dir =
2793     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
2794
2795   /* (may use ".sort_priority" from "setup_file_hash" above) */
2796   artwork_new->color = ARTWORKCOLOR(artwork_new);
2797
2798   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2799
2800   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2801   {
2802     if (strEqual(artwork_new->subdir, "."))
2803     {
2804       if (artwork_new->user_defined)
2805       {
2806         setString(&artwork_new->identifier, "private");
2807         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2808       }
2809       else
2810       {
2811         setString(&artwork_new->identifier, "classic");
2812         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2813       }
2814
2815       /* set to new values after changing ".sort_priority" */
2816       artwork_new->color = ARTWORKCOLOR(artwork_new);
2817
2818       setString(&artwork_new->class_desc,
2819                 getLevelClassDescription(artwork_new));
2820     }
2821     else
2822     {
2823       setString(&artwork_new->identifier, artwork_new->subdir);
2824     }
2825
2826     setString(&artwork_new->name, artwork_new->identifier);
2827     setString(&artwork_new->name_sorting, artwork_new->name);
2828   }
2829
2830 #if 0
2831   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2832 #endif
2833
2834   pushTreeInfo(node_first, artwork_new);
2835
2836   freeSetupFileHash(setup_file_hash);
2837
2838   free(directory_path);
2839   free(filename);
2840
2841   return TRUE;
2842 }
2843
2844 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2845                                           TreeInfo *node_parent,
2846                                           char *base_directory, int type)
2847 {
2848   DIR *dir;
2849   struct dirent *dir_entry;
2850   boolean valid_entry_found = FALSE;
2851
2852   if ((dir = opendir(base_directory)) == NULL)
2853   {
2854     /* display error if directory is main "options.graphics_directory" etc. */
2855     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2856       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2857
2858     return;
2859   }
2860
2861   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2862   {
2863     struct stat file_status;
2864     char *directory_name = dir_entry->d_name;
2865     char *directory_path = getPath2(base_directory, directory_name);
2866
2867     /* skip directory entries for current and parent directory */
2868     if (strEqual(directory_name, ".") ||
2869         strEqual(directory_name, ".."))
2870     {
2871       free(directory_path);
2872       continue;
2873     }
2874
2875     /* skip directory entries which are not a directory or are not accessible */
2876     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2877         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2878     {
2879       free(directory_path);
2880       continue;
2881     }
2882
2883     free(directory_path);
2884
2885     /* check if this directory contains artwork with or without config file */
2886     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2887                                                         base_directory,
2888                                                         directory_name, type);
2889   }
2890
2891   closedir(dir);
2892
2893   /* check if this directory directly contains artwork itself */
2894   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
2895                                                       base_directory, ".",
2896                                                       type);
2897   if (!valid_entry_found)
2898     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2899           base_directory);
2900 }
2901
2902 static TreeInfo *getDummyArtworkInfo(int type)
2903 {
2904   /* this is only needed when there is completely no artwork available */
2905   TreeInfo *artwork_new = newTreeInfo();
2906
2907   setTreeInfoToDefaults(artwork_new, type);
2908
2909   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
2910   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2911   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2912
2913   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2914   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2915   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2916
2917   return artwork_new;
2918 }
2919
2920 void LoadArtworkInfo()
2921 {
2922   LoadArtworkInfoCache();
2923
2924   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
2925
2926   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2927                                 options.graphics_directory,
2928                                 TREE_TYPE_GRAPHICS_DIR);
2929   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2930                                 getUserGraphicsDir(),
2931                                 TREE_TYPE_GRAPHICS_DIR);
2932
2933   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2934                                 options.sounds_directory,
2935                                 TREE_TYPE_SOUNDS_DIR);
2936   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2937                                 getUserSoundsDir(),
2938                                 TREE_TYPE_SOUNDS_DIR);
2939
2940   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2941                                 options.music_directory,
2942                                 TREE_TYPE_MUSIC_DIR);
2943   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2944                                 getUserMusicDir(),
2945                                 TREE_TYPE_MUSIC_DIR);
2946
2947   if (artwork.gfx_first == NULL)
2948     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2949   if (artwork.snd_first == NULL)
2950     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2951   if (artwork.mus_first == NULL)
2952     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2953
2954   /* before sorting, the first entries will be from the user directory */
2955   artwork.gfx_current =
2956     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2957   if (artwork.gfx_current == NULL)
2958     artwork.gfx_current =
2959       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2960   if (artwork.gfx_current == NULL)
2961     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2962
2963   artwork.snd_current =
2964     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2965   if (artwork.snd_current == NULL)
2966     artwork.snd_current =
2967       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2968   if (artwork.snd_current == NULL)
2969     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2970
2971   artwork.mus_current =
2972     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2973   if (artwork.mus_current == NULL)
2974     artwork.mus_current =
2975       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2976   if (artwork.mus_current == NULL)
2977     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2978
2979   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2980   artwork.snd_current_identifier = artwork.snd_current->identifier;
2981   artwork.mus_current_identifier = artwork.mus_current->identifier;
2982
2983 #if 0
2984   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2985   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2986   printf("music set == %s\n\n", artwork.mus_current_identifier);
2987 #endif
2988
2989   sortTreeInfo(&artwork.gfx_first);
2990   sortTreeInfo(&artwork.snd_first);
2991   sortTreeInfo(&artwork.mus_first);
2992
2993 #if 0
2994   dumpTreeInfo(artwork.gfx_first, 0);
2995   dumpTreeInfo(artwork.snd_first, 0);
2996   dumpTreeInfo(artwork.mus_first, 0);
2997 #endif
2998 }
2999
3000 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3001                                   LevelDirTree *level_node)
3002 {
3003   int type = (*artwork_node)->type;
3004
3005   /* recursively check all level directories for artwork sub-directories */
3006
3007   while (level_node)
3008   {
3009     /* check all tree entries for artwork, but skip parent link entries */
3010     if (!level_node->parent_link)
3011     {
3012       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3013       boolean cached = (artwork_new != NULL);
3014
3015       if (cached)
3016       {
3017         pushTreeInfo(artwork_node, artwork_new);
3018       }
3019       else
3020       {
3021         TreeInfo *topnode_last = *artwork_node;
3022         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3023                               ARTWORK_DIRECTORY(type));
3024
3025         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3026
3027         if (topnode_last != *artwork_node)      /* check for newly added node */
3028         {
3029           artwork_new = *artwork_node;
3030
3031           setString(&artwork_new->identifier,   level_node->subdir);
3032           setString(&artwork_new->name,         level_node->name);
3033           setString(&artwork_new->name_sorting, level_node->name_sorting);
3034
3035           artwork_new->sort_priority = level_node->sort_priority;
3036           artwork_new->color = LEVELCOLOR(artwork_new);
3037         }
3038
3039         free(path);
3040       }
3041
3042       /* insert artwork info (from old cache or filesystem) into new cache */
3043       if (artwork_new != NULL)
3044         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3045     }
3046
3047 #if 1
3048     if (level_node->level_group)
3049       DrawInitText(level_node->name, 150, FC_YELLOW);
3050 #endif
3051
3052     if (level_node->node_group != NULL)
3053       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3054
3055     level_node = level_node->next;
3056   }
3057 }
3058
3059 void LoadLevelArtworkInfo()
3060 {
3061   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3062
3063   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3064   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3065   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3066
3067   SaveArtworkInfoCache();
3068
3069   /* needed for reloading level artwork not known at ealier stage */
3070
3071   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3072   {
3073     artwork.gfx_current =
3074       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3075     if (artwork.gfx_current == NULL)
3076       artwork.gfx_current =
3077         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3078     if (artwork.gfx_current == NULL)
3079       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3080   }
3081
3082   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3083   {
3084     artwork.snd_current =
3085       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3086     if (artwork.snd_current == NULL)
3087       artwork.snd_current =
3088         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3089     if (artwork.snd_current == NULL)
3090       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3091   }
3092
3093   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3094   {
3095     artwork.mus_current =
3096       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3097     if (artwork.mus_current == NULL)
3098       artwork.mus_current =
3099         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3100     if (artwork.mus_current == NULL)
3101       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3102   }
3103
3104   sortTreeInfo(&artwork.gfx_first);
3105   sortTreeInfo(&artwork.snd_first);
3106   sortTreeInfo(&artwork.mus_first);
3107
3108 #if 0
3109   dumpTreeInfo(artwork.gfx_first, 0);
3110   dumpTreeInfo(artwork.snd_first, 0);
3111   dumpTreeInfo(artwork.mus_first, 0);
3112 #endif
3113 }
3114
3115 static void SaveUserLevelInfo()
3116 {
3117   LevelDirTree *level_info;
3118   char *filename;
3119   FILE *file;
3120   int i;
3121
3122   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3123
3124   if (!(file = fopen(filename, MODE_WRITE)))
3125   {
3126     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3127     free(filename);
3128     return;
3129   }
3130
3131   level_info = newTreeInfo();
3132
3133   /* always start with reliable default values */
3134   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3135
3136   setString(&level_info->name, getLoginName());
3137   setString(&level_info->author, getRealName());
3138   level_info->levels = 100;
3139   level_info->first_level = 1;
3140
3141   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3142
3143   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3144                                                  getCookie("LEVELINFO")));
3145
3146   ldi = *level_info;
3147   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3148   {
3149     if (i == LEVELINFO_TOKEN_NAME ||
3150         i == LEVELINFO_TOKEN_AUTHOR ||
3151         i == LEVELINFO_TOKEN_LEVELS ||
3152         i == LEVELINFO_TOKEN_FIRST_LEVEL)
3153       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3154
3155     /* just to make things nicer :) */
3156     if (i == LEVELINFO_TOKEN_AUTHOR)
3157       fprintf(file, "\n");      
3158   }
3159
3160   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3161
3162   fclose(file);
3163
3164   SetFilePermissions(filename, PERMS_PRIVATE);
3165
3166   freeTreeInfo(level_info);
3167   free(filename);
3168 }
3169
3170 char *getSetupValue(int type, void *value)
3171 {
3172   static char value_string[MAX_LINE_LEN];
3173
3174   if (value == NULL)
3175     return NULL;
3176
3177   switch (type)
3178   {
3179     case TYPE_BOOLEAN:
3180       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3181       break;
3182
3183     case TYPE_SWITCH:
3184       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3185       break;
3186
3187     case TYPE_YES_NO:
3188       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3189       break;
3190
3191     case TYPE_ECS_AGA:
3192       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3193       break;
3194
3195     case TYPE_KEY:
3196       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3197       break;
3198
3199     case TYPE_KEY_X11:
3200       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3201       break;
3202
3203     case TYPE_INTEGER:
3204       sprintf(value_string, "%d", *(int *)value);
3205       break;
3206
3207     case TYPE_STRING:
3208       if (*(char **)value == NULL)
3209         return NULL;
3210
3211       strcpy(value_string, *(char **)value);
3212       break;
3213
3214     default:
3215       value_string[0] = '\0';
3216       break;
3217   }
3218
3219   if (type & TYPE_GHOSTED)
3220     strcpy(value_string, "n/a");
3221
3222   return value_string;
3223 }
3224
3225 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3226 {
3227   int i;
3228   char *line;
3229   static char token_string[MAX_LINE_LEN];
3230   int token_type = token_info[token_nr].type;
3231   void *setup_value = token_info[token_nr].value;
3232   char *token_text = token_info[token_nr].text;
3233   char *value_string = getSetupValue(token_type, setup_value);
3234
3235   /* build complete token string */
3236   sprintf(token_string, "%s%s", prefix, token_text);
3237
3238   /* build setup entry line */
3239   line = getFormattedSetupEntry(token_string, value_string);
3240
3241   if (token_type == TYPE_KEY_X11)
3242   {
3243     Key key = *(Key *)setup_value;
3244     char *keyname = getKeyNameFromKey(key);
3245
3246     /* add comment, if useful */
3247     if (!strEqual(keyname, "(undefined)") &&
3248         !strEqual(keyname, "(unknown)"))
3249     {
3250       /* add at least one whitespace */
3251       strcat(line, " ");
3252       for (i = strlen(line); i < token_comment_position; i++)
3253         strcat(line, " ");
3254
3255       strcat(line, "# ");
3256       strcat(line, keyname);
3257     }
3258   }
3259
3260   return line;
3261 }
3262
3263 void LoadLevelSetup_LastSeries()
3264 {
3265   /* ----------------------------------------------------------------------- */
3266   /* ~/.<program>/levelsetup.conf                                            */
3267   /* ----------------------------------------------------------------------- */
3268
3269   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3270   SetupFileHash *level_setup_hash = NULL;
3271
3272   /* always start with reliable default values */
3273   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3274
3275   if ((level_setup_hash = loadSetupFileHash(filename)))
3276   {
3277     char *last_level_series =
3278       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3279
3280     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3281                                                  last_level_series);
3282     if (leveldir_current == NULL)
3283       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3284
3285     checkSetupFileHashIdentifier(level_setup_hash, filename,
3286                                  getCookie("LEVELSETUP"));
3287
3288     freeSetupFileHash(level_setup_hash);
3289   }
3290   else
3291     Error(ERR_WARN, "using default setup values");
3292
3293   free(filename);
3294 }
3295
3296 void SaveLevelSetup_LastSeries()
3297 {
3298   /* ----------------------------------------------------------------------- */
3299   /* ~/.<program>/levelsetup.conf                                            */
3300   /* ----------------------------------------------------------------------- */
3301
3302   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3303   char *level_subdir = leveldir_current->subdir;
3304   FILE *file;
3305
3306   InitUserDataDirectory();
3307
3308   if (!(file = fopen(filename, MODE_WRITE)))
3309   {
3310     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3311     free(filename);
3312     return;
3313   }
3314
3315   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3316                                                  getCookie("LEVELSETUP")));
3317   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3318                                                level_subdir));
3319
3320   fclose(file);
3321
3322   SetFilePermissions(filename, PERMS_PRIVATE);
3323
3324   free(filename);
3325 }
3326
3327 static void checkSeriesInfo()
3328 {
3329   static char *level_directory = NULL;
3330   DIR *dir;
3331   struct dirent *dir_entry;
3332
3333   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3334
3335   level_directory = getPath2((leveldir_current->in_user_dir ?
3336                               getUserLevelDir(NULL) :
3337                               options.level_directory),
3338                              leveldir_current->fullpath);
3339
3340   if ((dir = opendir(level_directory)) == NULL)
3341   {
3342     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3343     return;
3344   }
3345
3346   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
3347   {
3348     if (strlen(dir_entry->d_name) > 4 &&
3349         dir_entry->d_name[3] == '.' &&
3350         strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3351     {
3352       char levelnum_str[4];
3353       int levelnum_value;
3354
3355       strncpy(levelnum_str, dir_entry->d_name, 3);
3356       levelnum_str[3] = '\0';
3357
3358       levelnum_value = atoi(levelnum_str);
3359
3360 #if 0
3361       if (levelnum_value < leveldir_current->first_level)
3362       {
3363         Error(ERR_WARN, "additional level %d found", levelnum_value);
3364         leveldir_current->first_level = levelnum_value;
3365       }
3366       else if (levelnum_value > leveldir_current->last_level)
3367       {
3368         Error(ERR_WARN, "additional level %d found", levelnum_value);
3369         leveldir_current->last_level = levelnum_value;
3370       }
3371 #endif
3372     }
3373   }
3374
3375   closedir(dir);
3376 }
3377
3378 void LoadLevelSetup_SeriesInfo()
3379 {
3380   char *filename;
3381   SetupFileHash *level_setup_hash = NULL;
3382   char *level_subdir = leveldir_current->subdir;
3383
3384   /* always start with reliable default values */
3385   level_nr = leveldir_current->first_level;
3386
3387   checkSeriesInfo(leveldir_current);
3388
3389   /* ----------------------------------------------------------------------- */
3390   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
3391   /* ----------------------------------------------------------------------- */
3392
3393   level_subdir = leveldir_current->subdir;
3394
3395   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3396
3397   if ((level_setup_hash = loadSetupFileHash(filename)))
3398   {
3399     char *token_value;
3400
3401     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3402
3403     if (token_value)
3404     {
3405       level_nr = atoi(token_value);
3406
3407       if (level_nr < leveldir_current->first_level)
3408         level_nr = leveldir_current->first_level;
3409       if (level_nr > leveldir_current->last_level)
3410         level_nr = leveldir_current->last_level;
3411     }
3412
3413     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3414
3415     if (token_value)
3416     {
3417       int level_nr = atoi(token_value);
3418
3419       if (level_nr < leveldir_current->first_level)
3420         level_nr = leveldir_current->first_level;
3421       if (level_nr > leveldir_current->last_level + 1)
3422         level_nr = leveldir_current->last_level;
3423
3424       if (leveldir_current->user_defined || !leveldir_current->handicap)
3425         level_nr = leveldir_current->last_level;
3426
3427       leveldir_current->handicap_level = level_nr;
3428     }
3429
3430     checkSetupFileHashIdentifier(level_setup_hash, filename,
3431                                  getCookie("LEVELSETUP"));
3432
3433     freeSetupFileHash(level_setup_hash);
3434   }
3435   else
3436     Error(ERR_WARN, "using default setup values");
3437
3438   free(filename);
3439 }
3440
3441 void SaveLevelSetup_SeriesInfo()
3442 {
3443   char *filename;
3444   char *level_subdir = leveldir_current->subdir;
3445   char *level_nr_str = int2str(level_nr, 0);
3446   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3447   FILE *file;
3448
3449   /* ----------------------------------------------------------------------- */
3450   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
3451   /* ----------------------------------------------------------------------- */
3452
3453   InitLevelSetupDirectory(level_subdir);
3454
3455   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3456
3457   if (!(file = fopen(filename, MODE_WRITE)))
3458   {
3459     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3460     free(filename);
3461     return;
3462   }
3463
3464   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3465                                                  getCookie("LEVELSETUP")));
3466   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3467                                                level_nr_str));
3468   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3469                                                handicap_level_str));
3470
3471   fclose(file);
3472
3473   SetFilePermissions(filename, PERMS_PRIVATE);
3474
3475   free(filename);
3476 }