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