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