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