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