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