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