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