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