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