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