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