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