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