cleanup of saving high score files for single/multi-user installations
[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   static char *score_level_dir = NULL;
137   char *score_subdir = SCORES_DIRECTORY;
138
139   if (score_dir == NULL)
140   {
141     if (program.global_scores)
142       score_dir = getPath2(getCommonDataDir(),   score_subdir);
143     else
144       score_dir = getPath2(getUserGameDataDir(), score_subdir);
145   }
146
147   if (level_subdir != NULL)
148   {
149     checked_free(score_level_dir);
150
151     score_level_dir = getPath2(score_dir, level_subdir);
152
153     return score_level_dir;
154   }
155
156   return score_dir;
157 }
158
159 static char *getLevelSetupDir(char *level_subdir)
160 {
161   static char *levelsetup_dir = NULL;
162   char *data_dir = getUserGameDataDir();
163   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
164
165   checked_free(levelsetup_dir);
166
167   if (level_subdir != NULL)
168     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
169   else
170     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
171
172   return levelsetup_dir;
173 }
174
175 static char *getCacheDir()
176 {
177   static char *cache_dir = NULL;
178
179   if (cache_dir == NULL)
180     cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
181
182   return cache_dir;
183 }
184
185 static char *getLevelDirFromTreeInfo(TreeInfo *node)
186 {
187   static char *level_dir = NULL;
188
189   if (node == NULL)
190     return options.level_directory;
191
192   checked_free(level_dir);
193
194   level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
195                         options.level_directory), node->fullpath);
196
197   return level_dir;
198 }
199
200 char *getCurrentLevelDir()
201 {
202   return getLevelDirFromTreeInfo(leveldir_current);
203 }
204
205 static char *getTapeDir(char *level_subdir)
206 {
207   static char *tape_dir = NULL;
208   char *data_dir = getUserGameDataDir();
209   char *tape_subdir = TAPES_DIRECTORY;
210
211   checked_free(tape_dir);
212
213   if (level_subdir != NULL)
214     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
215   else
216     tape_dir = getPath2(data_dir, tape_subdir);
217
218   return tape_dir;
219 }
220
221 static char *getSolutionTapeDir()
222 {
223   static char *tape_dir = NULL;
224   char *data_dir = getCurrentLevelDir();
225   char *tape_subdir = TAPES_DIRECTORY;
226
227   checked_free(tape_dir);
228
229   tape_dir = getPath2(data_dir, tape_subdir);
230
231   return tape_dir;
232 }
233
234 static char *getDefaultGraphicsDir(char *graphics_subdir)
235 {
236   static char *graphics_dir = NULL;
237
238   if (graphics_subdir == NULL)
239     return options.graphics_directory;
240
241   checked_free(graphics_dir);
242
243   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
244
245   return graphics_dir;
246 }
247
248 static char *getDefaultSoundsDir(char *sounds_subdir)
249 {
250   static char *sounds_dir = NULL;
251
252   if (sounds_subdir == NULL)
253     return options.sounds_directory;
254
255   checked_free(sounds_dir);
256
257   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
258
259   return sounds_dir;
260 }
261
262 static char *getDefaultMusicDir(char *music_subdir)
263 {
264   static char *music_dir = NULL;
265
266   if (music_subdir == NULL)
267     return options.music_directory;
268
269   checked_free(music_dir);
270
271   music_dir = getPath2(options.music_directory, music_subdir);
272
273   return music_dir;
274 }
275
276 static char *getClassicArtworkSet(int type)
277 {
278   return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
279           type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
280           type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
281 }
282
283 static char *getClassicArtworkDir(int type)
284 {
285   return (type == TREE_TYPE_GRAPHICS_DIR ?
286           getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
287           type == TREE_TYPE_SOUNDS_DIR ?
288           getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
289           type == TREE_TYPE_MUSIC_DIR ?
290           getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
291 }
292
293 static char *getUserGraphicsDir()
294 {
295   static char *usergraphics_dir = NULL;
296
297   if (usergraphics_dir == NULL)
298     usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
299
300   return usergraphics_dir;
301 }
302
303 static char *getUserSoundsDir()
304 {
305   static char *usersounds_dir = NULL;
306
307   if (usersounds_dir == NULL)
308     usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
309
310   return usersounds_dir;
311 }
312
313 static char *getUserMusicDir()
314 {
315   static char *usermusic_dir = NULL;
316
317   if (usermusic_dir == NULL)
318     usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
319
320   return usermusic_dir;
321 }
322
323 static char *getSetupArtworkDir(TreeInfo *ti)
324 {
325   static char *artwork_dir = NULL;
326
327   if (ti == NULL)
328     return NULL;
329
330   checked_free(artwork_dir);
331
332   artwork_dir = getPath2(ti->basepath, ti->fullpath);
333
334   return artwork_dir;
335 }
336
337 char *setLevelArtworkDir(TreeInfo *ti)
338 {
339   char **artwork_path_ptr, **artwork_set_ptr;
340   TreeInfo *level_artwork;
341
342   if (ti == NULL || leveldir_current == NULL)
343     return NULL;
344
345   artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
346   artwork_set_ptr  = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
347
348   checked_free(*artwork_path_ptr);
349
350   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
351   {
352     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
353   }
354   else
355   {
356     /*
357       No (or non-existing) artwork configured in "levelinfo.conf". This would
358       normally result in using the artwork configured in the setup menu. But
359       if an artwork subdirectory exists (which might contain custom artwork
360       or an artwork configuration file), this level artwork must be treated
361       as relative to the default "classic" artwork, not to the artwork that
362       is currently configured in the setup menu.
363
364       Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
365       the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
366       the real "classic" artwork from the original R'n'D (like "gfx_classic").
367     */
368
369     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
370
371     checked_free(*artwork_set_ptr);
372
373     if (directoryExists(dir))
374     {
375       *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
376       *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
377     }
378     else
379     {
380       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
381       *artwork_set_ptr = NULL;
382     }
383
384     free(dir);
385   }
386
387   return *artwork_set_ptr;
388 }
389
390 inline static char *getLevelArtworkSet(int type)
391 {
392   if (leveldir_current == NULL)
393     return NULL;
394
395   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
396 }
397
398 inline static char *getLevelArtworkDir(int type)
399 {
400   if (leveldir_current == NULL)
401     return UNDEFINED_FILENAME;
402
403   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
404 }
405
406 char *getProgramConfigFilename(char *command_filename_ptr)
407 {
408   char *command_filename_1 = getStringCopy(command_filename_ptr);
409
410   // strip trailing executable suffix from command filename
411   if (strSuffix(command_filename_1, ".exe"))
412     command_filename_1[strlen(command_filename_1) - 4] = '\0';
413
414   char *command_basepath = getBasePath(command_filename_ptr);
415   char *command_basename = getBaseNameNoSuffix(command_filename_ptr);
416   char *command_filename_2 = getPath2(command_basepath, command_basename);
417
418   char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
419   char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
420
421   // 1st try: look for config file that exactly matches the binary filename
422   if (fileExists(config_filename_1))
423     return config_filename_1;
424
425   // 2nd try: return config filename that matches binary filename without suffix
426   return config_filename_2;
427 }
428
429 char *getTapeFilename(int nr)
430 {
431   static char *filename = NULL;
432   char basename[MAX_FILENAME_LEN];
433
434   checked_free(filename);
435
436   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
437   filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
438
439   return filename;
440 }
441
442 char *getSolutionTapeFilename(int nr)
443 {
444   static char *filename = NULL;
445   char basename[MAX_FILENAME_LEN];
446
447   checked_free(filename);
448
449   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
450   filename = getPath2(getSolutionTapeDir(), basename);
451
452   if (!fileExists(filename))
453   {
454     static char *filename_sln = NULL;
455
456     checked_free(filename_sln);
457
458     sprintf(basename, "%03d.sln", nr);
459     filename_sln = getPath2(getSolutionTapeDir(), basename);
460
461     if (fileExists(filename_sln))
462       return filename_sln;
463   }
464
465   return filename;
466 }
467
468 char *getScoreFilename(int nr)
469 {
470   static char *filename = NULL;
471   char basename[MAX_FILENAME_LEN];
472
473   checked_free(filename);
474
475   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
476   filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
477
478   return filename;
479 }
480
481 char *getSetupFilename()
482 {
483   static char *filename = NULL;
484
485   checked_free(filename);
486
487   filename = getPath2(getSetupDir(), SETUP_FILENAME);
488
489   return filename;
490 }
491
492 char *getDefaultSetupFilename()
493 {
494   return program.config_filename;
495 }
496
497 char *getEditorSetupFilename()
498 {
499   static char *filename = NULL;
500
501   checked_free(filename);
502   filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
503
504   if (fileExists(filename))
505     return filename;
506
507   checked_free(filename);
508   filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
509
510   return filename;
511 }
512
513 char *getHelpAnimFilename()
514 {
515   static char *filename = NULL;
516
517   checked_free(filename);
518
519   filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
520
521   return filename;
522 }
523
524 char *getHelpTextFilename()
525 {
526   static char *filename = NULL;
527
528   checked_free(filename);
529
530   filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
531
532   return filename;
533 }
534
535 char *getLevelSetInfoFilename()
536 {
537   static char *filename = NULL;
538   char *basenames[] =
539   {
540     "README",
541     "README.TXT",
542     "README.txt",
543     "Readme",
544     "Readme.txt",
545     "readme",
546     "readme.txt",
547
548     NULL
549   };
550   int i;
551
552   for (i = 0; basenames[i] != NULL; i++)
553   {
554     checked_free(filename);
555     filename = getPath2(getCurrentLevelDir(), basenames[i]);
556
557     if (fileExists(filename))
558       return filename;
559   }
560
561   return NULL;
562 }
563
564 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
565 {
566   static char basename[32];
567
568   sprintf(basename, "%s_%d.txt",
569           (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
570
571   return basename;
572 }
573
574 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
575 {
576   static char *filename = NULL;
577   char *basename;
578   boolean skip_setup_artwork = FALSE;
579
580   checked_free(filename);
581
582   basename = getLevelSetTitleMessageBasename(nr, initial);
583
584   if (!gfx.override_level_graphics)
585   {
586     /* 1st try: look for special artwork in current level series directory */
587     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
588     if (fileExists(filename))
589       return filename;
590
591     free(filename);
592
593     /* 2nd try: look for message file in current level set directory */
594     filename = getPath2(getCurrentLevelDir(), basename);
595     if (fileExists(filename))
596       return filename;
597
598     free(filename);
599
600     /* check if there is special artwork configured in level series config */
601     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
602     {
603       /* 3rd try: look for special artwork configured in level series config */
604       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
605       if (fileExists(filename))
606         return filename;
607
608       free(filename);
609
610       /* take missing artwork configured in level set config from default */
611       skip_setup_artwork = TRUE;
612     }
613   }
614
615   if (!skip_setup_artwork)
616   {
617     /* 4th try: look for special artwork in configured artwork directory */
618     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
619     if (fileExists(filename))
620       return filename;
621
622     free(filename);
623   }
624
625   /* 5th try: look for default artwork in new default artwork directory */
626   filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
627   if (fileExists(filename))
628     return filename;
629
630   free(filename);
631
632   /* 6th try: look for default artwork in old default artwork directory */
633   filename = getPath2(options.graphics_directory, basename);
634   if (fileExists(filename))
635     return filename;
636
637   return NULL;          /* cannot find specified artwork file anywhere */
638 }
639
640 static char *getCorrectedArtworkBasename(char *basename)
641 {
642   return basename;
643 }
644
645 char *getCustomImageFilename(char *basename)
646 {
647   static char *filename = NULL;
648   boolean skip_setup_artwork = FALSE;
649
650   checked_free(filename);
651
652   basename = getCorrectedArtworkBasename(basename);
653
654   if (!gfx.override_level_graphics)
655   {
656     /* 1st try: look for special artwork in current level series directory */
657     filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
658     if (fileExists(filename))
659       return filename;
660
661     free(filename);
662
663     /* check if there is special artwork configured in level series config */
664     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
665     {
666       /* 2nd try: look for special artwork configured in level series config */
667       filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
668       if (fileExists(filename))
669         return filename;
670
671       free(filename);
672
673       /* take missing artwork configured in level set config from default */
674       skip_setup_artwork = TRUE;
675     }
676   }
677
678   if (!skip_setup_artwork)
679   {
680     /* 3rd try: look for special artwork in configured artwork directory */
681     filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
682     if (fileExists(filename))
683       return filename;
684
685     free(filename);
686   }
687
688   /* 4th try: look for default artwork in new default artwork directory */
689   filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
690   if (fileExists(filename))
691     return filename;
692
693   free(filename);
694
695   /* 5th try: look for default artwork in old default artwork directory */
696   filename = getImg2(options.graphics_directory, basename);
697   if (fileExists(filename))
698     return filename;
699
700   if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
701   {
702     free(filename);
703
704     if (options.debug)
705       Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
706             basename);
707
708     /* 6th try: look for fallback artwork in old default artwork directory */
709     /* (needed to prevent errors when trying to access unused artwork files) */
710     filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
711     if (fileExists(filename))
712       return filename;
713   }
714
715   return NULL;          /* cannot find specified artwork file anywhere */
716 }
717
718 char *getCustomSoundFilename(char *basename)
719 {
720   static char *filename = NULL;
721   boolean skip_setup_artwork = FALSE;
722
723   checked_free(filename);
724
725   basename = getCorrectedArtworkBasename(basename);
726
727   if (!gfx.override_level_sounds)
728   {
729     /* 1st try: look for special artwork in current level series directory */
730     filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
731     if (fileExists(filename))
732       return filename;
733
734     free(filename);
735
736     /* check if there is special artwork configured in level series config */
737     if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
738     {
739       /* 2nd try: look for special artwork configured in level series config */
740       filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
741       if (fileExists(filename))
742         return filename;
743
744       free(filename);
745
746       /* take missing artwork configured in level set config from default */
747       skip_setup_artwork = TRUE;
748     }
749   }
750
751   if (!skip_setup_artwork)
752   {
753     /* 3rd try: look for special artwork in configured artwork directory */
754     filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
755     if (fileExists(filename))
756       return filename;
757
758     free(filename);
759   }
760
761   /* 4th try: look for default artwork in new default artwork directory */
762   filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
763   if (fileExists(filename))
764     return filename;
765
766   free(filename);
767
768   /* 5th try: look for default artwork in old default artwork directory */
769   filename = getPath2(options.sounds_directory, basename);
770   if (fileExists(filename))
771     return filename;
772
773   if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
774   {
775     free(filename);
776
777     if (options.debug)
778       Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
779             basename);
780
781     /* 6th try: look for fallback artwork in old default artwork directory */
782     /* (needed to prevent errors when trying to access unused artwork files) */
783     filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
784     if (fileExists(filename))
785       return filename;
786   }
787
788   return NULL;          /* cannot find specified artwork file anywhere */
789 }
790
791 char *getCustomMusicFilename(char *basename)
792 {
793   static char *filename = NULL;
794   boolean skip_setup_artwork = FALSE;
795
796   checked_free(filename);
797
798   basename = getCorrectedArtworkBasename(basename);
799
800   if (!gfx.override_level_music)
801   {
802     /* 1st try: look for special artwork in current level series directory */
803     filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
804     if (fileExists(filename))
805       return filename;
806
807     free(filename);
808
809     /* check if there is special artwork configured in level series config */
810     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
811     {
812       /* 2nd try: look for special artwork configured in level series config */
813       filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
814       if (fileExists(filename))
815         return filename;
816
817       free(filename);
818
819       /* take missing artwork configured in level set config from default */
820       skip_setup_artwork = TRUE;
821     }
822   }
823
824   if (!skip_setup_artwork)
825   {
826     /* 3rd try: look for special artwork in configured artwork directory */
827     filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
828     if (fileExists(filename))
829       return filename;
830
831     free(filename);
832   }
833
834   /* 4th try: look for default artwork in new default artwork directory */
835   filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
836   if (fileExists(filename))
837     return filename;
838
839   free(filename);
840
841   /* 5th try: look for default artwork in old default artwork directory */
842   filename = getPath2(options.music_directory, basename);
843   if (fileExists(filename))
844     return filename;
845
846   if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
847   {
848     free(filename);
849
850     if (options.debug)
851       Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
852             basename);
853
854     /* 6th try: look for fallback artwork in old default artwork directory */
855     /* (needed to prevent errors when trying to access unused artwork files) */
856     filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
857     if (fileExists(filename))
858       return filename;
859   }
860
861   return NULL;          /* cannot find specified artwork file anywhere */
862 }
863
864 char *getCustomArtworkFilename(char *basename, int type)
865 {
866   if (type == ARTWORK_TYPE_GRAPHICS)
867     return getCustomImageFilename(basename);
868   else if (type == ARTWORK_TYPE_SOUNDS)
869     return getCustomSoundFilename(basename);
870   else if (type == ARTWORK_TYPE_MUSIC)
871     return getCustomMusicFilename(basename);
872   else
873     return UNDEFINED_FILENAME;
874 }
875
876 char *getCustomArtworkConfigFilename(int type)
877 {
878   return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
879 }
880
881 char *getCustomArtworkLevelConfigFilename(int type)
882 {
883   static char *filename = NULL;
884
885   checked_free(filename);
886
887   filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
888
889   return filename;
890 }
891
892 char *getCustomMusicDirectory(void)
893 {
894   static char *directory = NULL;
895   boolean skip_setup_artwork = FALSE;
896
897   checked_free(directory);
898
899   if (!gfx.override_level_music)
900   {
901     /* 1st try: look for special artwork in current level series directory */
902     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
903     if (directoryExists(directory))
904       return directory;
905
906     free(directory);
907
908     /* check if there is special artwork configured in level series config */
909     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
910     {
911       /* 2nd try: look for special artwork configured in level series config */
912       directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
913       if (directoryExists(directory))
914         return directory;
915
916       free(directory);
917
918       /* take missing artwork configured in level set config from default */
919       skip_setup_artwork = TRUE;
920     }
921   }
922
923   if (!skip_setup_artwork)
924   {
925     /* 3rd try: look for special artwork in configured artwork directory */
926     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
927     if (directoryExists(directory))
928       return directory;
929
930     free(directory);
931   }
932
933   /* 4th try: look for default artwork in new default artwork directory */
934   directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
935   if (directoryExists(directory))
936     return directory;
937
938   free(directory);
939
940   /* 5th try: look for default artwork in old default artwork directory */
941   directory = getStringCopy(options.music_directory);
942   if (directoryExists(directory))
943     return directory;
944
945   return NULL;          /* cannot find specified artwork file anywhere */
946 }
947
948 void InitTapeDirectory(char *level_subdir)
949 {
950   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
951   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
952   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
953 }
954
955 void InitScoreDirectory(char *level_subdir)
956 {
957   int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
958
959   if (program.global_scores)
960     createDirectory(getCommonDataDir(), "common data", permissions);
961   else
962     createDirectory(getUserGameDataDir(), "user data", permissions);
963
964   createDirectory(getScoreDir(NULL), "main score", permissions);
965   createDirectory(getScoreDir(level_subdir), "level score", permissions);
966 }
967
968 static void SaveUserLevelInfo();
969
970 void InitUserLevelDirectory(char *level_subdir)
971 {
972   if (!directoryExists(getUserLevelDir(level_subdir)))
973   {
974     createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
975     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
976     createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
977
978     SaveUserLevelInfo();
979   }
980 }
981
982 void InitLevelSetupDirectory(char *level_subdir)
983 {
984   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
985   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
986   createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
987 }
988
989 void InitCacheDirectory()
990 {
991   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
992   createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
993 }
994
995
996 /* ------------------------------------------------------------------------- */
997 /* some functions to handle lists of level and artwork directories           */
998 /* ------------------------------------------------------------------------- */
999
1000 TreeInfo *newTreeInfo()
1001 {
1002   return checked_calloc(sizeof(TreeInfo));
1003 }
1004
1005 TreeInfo *newTreeInfo_setDefaults(int type)
1006 {
1007   TreeInfo *ti = newTreeInfo();
1008
1009   setTreeInfoToDefaults(ti, type);
1010
1011   return ti;
1012 }
1013
1014 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1015 {
1016   node_new->next = *node_first;
1017   *node_first = node_new;
1018 }
1019
1020 int numTreeInfo(TreeInfo *node)
1021 {
1022   int num = 0;
1023
1024   while (node)
1025   {
1026     num++;
1027     node = node->next;
1028   }
1029
1030   return num;
1031 }
1032
1033 boolean validLevelSeries(TreeInfo *node)
1034 {
1035   return (node != NULL && !node->node_group && !node->parent_link);
1036 }
1037
1038 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1039 {
1040   if (node == NULL)
1041     return NULL;
1042
1043   if (node->node_group)         /* enter level group (step down into tree) */
1044     return getFirstValidTreeInfoEntry(node->node_group);
1045   else if (node->parent_link)   /* skip start entry of level group */
1046   {
1047     if (node->next)             /* get first real level series entry */
1048       return getFirstValidTreeInfoEntry(node->next);
1049     else                        /* leave empty level group and go on */
1050       return getFirstValidTreeInfoEntry(node->node_parent->next);
1051   }
1052   else                          /* this seems to be a regular level series */
1053     return node;
1054 }
1055
1056 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1057 {
1058   if (node == NULL)
1059     return NULL;
1060
1061   if (node->node_parent == NULL)                /* top level group */
1062     return *node->node_top;
1063   else                                          /* sub level group */
1064     return node->node_parent->node_group;
1065 }
1066
1067 int numTreeInfoInGroup(TreeInfo *node)
1068 {
1069   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1070 }
1071
1072 int posTreeInfo(TreeInfo *node)
1073 {
1074   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1075   int pos = 0;
1076
1077   while (node_cmp)
1078   {
1079     if (node_cmp == node)
1080       return pos;
1081
1082     pos++;
1083     node_cmp = node_cmp->next;
1084   }
1085
1086   return 0;
1087 }
1088
1089 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1090 {
1091   TreeInfo *node_default = node;
1092   int pos_cmp = 0;
1093
1094   while (node)
1095   {
1096     if (pos_cmp == pos)
1097       return node;
1098
1099     pos_cmp++;
1100     node = node->next;
1101   }
1102
1103   return node_default;
1104 }
1105
1106 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1107 {
1108   if (identifier == NULL)
1109     return NULL;
1110
1111   while (node)
1112   {
1113     if (node->node_group)
1114     {
1115       TreeInfo *node_group;
1116
1117       node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1118
1119       if (node_group)
1120         return node_group;
1121     }
1122     else if (!node->parent_link)
1123     {
1124       if (strEqual(identifier, node->identifier))
1125         return node;
1126     }
1127
1128     node = node->next;
1129   }
1130
1131   return NULL;
1132 }
1133
1134 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1135                         TreeInfo *node, boolean skip_sets_without_levels)
1136 {
1137   TreeInfo *node_new;
1138
1139   if (node == NULL)
1140     return NULL;
1141
1142   if (!node->parent_link && !node->level_group &&
1143       skip_sets_without_levels && node->levels == 0)
1144     return cloneTreeNode(node_top, node_parent, node->next,
1145                          skip_sets_without_levels);
1146
1147   node_new = getTreeInfoCopy(node);             /* copy complete node */
1148
1149   node_new->node_top = node_top;                /* correct top node link */
1150   node_new->node_parent = node_parent;          /* correct parent node link */
1151
1152   if (node->level_group)
1153     node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1154                                          skip_sets_without_levels);
1155
1156   node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1157                                  skip_sets_without_levels);
1158   
1159   return node_new;
1160 }
1161
1162 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1163 {
1164   TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1165
1166   *ti_new = ti_cloned;
1167 }
1168
1169 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1170 {
1171   boolean settings_changed = FALSE;
1172
1173   while (node)
1174   {
1175     if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1176         !strEqual(node->graphics_set, node->graphics_set_ecs))
1177     {
1178       setString(&node->graphics_set, node->graphics_set_ecs);
1179       settings_changed = TRUE;
1180     }
1181     else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1182              !strEqual(node->graphics_set, node->graphics_set_aga))
1183     {
1184       setString(&node->graphics_set, node->graphics_set_aga);
1185       settings_changed = TRUE;
1186     }
1187
1188     if (node->node_group != NULL)
1189       settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1190
1191     node = node->next;
1192   }
1193
1194   return settings_changed;
1195 }
1196
1197 void dumpTreeInfo(TreeInfo *node, int depth)
1198 {
1199   int i;
1200
1201   printf("Dumping TreeInfo:\n");
1202
1203   while (node)
1204   {
1205     for (i = 0; i < (depth + 1) * 3; i++)
1206       printf(" ");
1207
1208     printf("'%s' / '%s'\n", node->identifier, node->name);
1209
1210     /*
1211     // use for dumping artwork info tree
1212     printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1213            node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1214     */
1215
1216     if (node->node_group != NULL)
1217       dumpTreeInfo(node->node_group, depth + 1);
1218
1219     node = node->next;
1220   }
1221 }
1222
1223 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1224                                 int (*compare_function)(const void *,
1225                                                         const void *))
1226 {
1227   int num_nodes = numTreeInfo(*node_first);
1228   TreeInfo **sort_array;
1229   TreeInfo *node = *node_first;
1230   int i = 0;
1231
1232   if (num_nodes == 0)
1233     return;
1234
1235   /* allocate array for sorting structure pointers */
1236   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1237
1238   /* writing structure pointers to sorting array */
1239   while (i < num_nodes && node)         /* double boundary check... */
1240   {
1241     sort_array[i] = node;
1242
1243     i++;
1244     node = node->next;
1245   }
1246
1247   /* sorting the structure pointers in the sorting array */
1248   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1249         compare_function);
1250
1251   /* update the linkage of list elements with the sorted node array */
1252   for (i = 0; i < num_nodes - 1; i++)
1253     sort_array[i]->next = sort_array[i + 1];
1254   sort_array[num_nodes - 1]->next = NULL;
1255
1256   /* update the linkage of the main list anchor pointer */
1257   *node_first = sort_array[0];
1258
1259   free(sort_array);
1260
1261   /* now recursively sort the level group structures */
1262   node = *node_first;
1263   while (node)
1264   {
1265     if (node->node_group != NULL)
1266       sortTreeInfoBySortFunction(&node->node_group, compare_function);
1267
1268     node = node->next;
1269   }
1270 }
1271
1272 void sortTreeInfo(TreeInfo **node_first)
1273 {
1274   sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1275 }
1276
1277
1278 /* ========================================================================= */
1279 /* some stuff from "files.c"                                                 */
1280 /* ========================================================================= */
1281
1282 #if defined(PLATFORM_WIN32)
1283 #ifndef S_IRGRP
1284 #define S_IRGRP S_IRUSR
1285 #endif
1286 #ifndef S_IROTH
1287 #define S_IROTH S_IRUSR
1288 #endif
1289 #ifndef S_IWGRP
1290 #define S_IWGRP S_IWUSR
1291 #endif
1292 #ifndef S_IWOTH
1293 #define S_IWOTH S_IWUSR
1294 #endif
1295 #ifndef S_IXGRP
1296 #define S_IXGRP S_IXUSR
1297 #endif
1298 #ifndef S_IXOTH
1299 #define S_IXOTH S_IXUSR
1300 #endif
1301 #ifndef S_IRWXG
1302 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1303 #endif
1304 #ifndef S_ISGID
1305 #define S_ISGID 0
1306 #endif
1307 #endif  /* PLATFORM_WIN32 */
1308
1309 /* file permissions for newly written files */
1310 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1311 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1312 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1313
1314 #define MODE_W_PRIVATE          (S_IWUSR)
1315 #define MODE_W_PUBLIC_FILE      (S_IWUSR | S_IWGRP)
1316 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1317
1318 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1319 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1320 #define DIR_PERMS_PUBLIC_ALL    (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1321
1322 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1323 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1324 #define FILE_PERMS_PUBLIC_ALL   (MODE_R_ALL | MODE_W_ALL)
1325
1326
1327 char *getHomeDir()
1328 {
1329   static char *dir = NULL;
1330
1331 #if defined(PLATFORM_WIN32)
1332   if (dir == NULL)
1333   {
1334     dir = checked_malloc(MAX_PATH + 1);
1335
1336     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1337       strcpy(dir, ".");
1338   }
1339 #elif defined(PLATFORM_UNIX)
1340   if (dir == NULL)
1341   {
1342     if ((dir = getenv("HOME")) == NULL)
1343     {
1344       struct passwd *pwd;
1345
1346       if ((pwd = getpwuid(getuid())) != NULL)
1347         dir = getStringCopy(pwd->pw_dir);
1348       else
1349         dir = ".";
1350     }
1351   }
1352 #else
1353   dir = ".";
1354 #endif
1355
1356   return dir;
1357 }
1358
1359 char *getCommonDataDir(void)
1360 {
1361   static char *common_data_dir = NULL;
1362
1363 #if defined(PLATFORM_WIN32)
1364   if (common_data_dir == NULL)
1365   {
1366     char *dir = checked_malloc(MAX_PATH + 1);
1367
1368     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1369         && !strEqual(dir, ""))          /* empty for Windows 95/98 */
1370       common_data_dir = getPath2(dir, program.userdata_subdir);
1371     else
1372       common_data_dir = options.rw_base_directory;
1373   }
1374 #else
1375   if (common_data_dir == NULL)
1376     common_data_dir = options.rw_base_directory;
1377 #endif
1378
1379   return common_data_dir;
1380 }
1381
1382 char *getPersonalDataDir(void)
1383 {
1384   static char *personal_data_dir = NULL;
1385
1386 #if defined(PLATFORM_MACOSX)
1387   if (personal_data_dir == NULL)
1388     personal_data_dir = getPath2(getHomeDir(), "Documents");
1389 #else
1390   if (personal_data_dir == NULL)
1391     personal_data_dir = getHomeDir();
1392 #endif
1393
1394   return personal_data_dir;
1395 }
1396
1397 char *getUserGameDataDir(void)
1398 {
1399   static char *user_game_data_dir = NULL;
1400
1401 #if defined(PLATFORM_ANDROID)
1402   if (user_game_data_dir == NULL)
1403     user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1404 #else
1405   if (user_game_data_dir == NULL)
1406     user_game_data_dir = getPath2(getPersonalDataDir(),
1407                                   program.userdata_subdir);
1408 #endif
1409
1410   return user_game_data_dir;
1411 }
1412
1413 char *getSetupDir()
1414 {
1415   return getUserGameDataDir();
1416 }
1417
1418 static mode_t posix_umask(mode_t mask)
1419 {
1420 #if defined(PLATFORM_UNIX)
1421   return umask(mask);
1422 #else
1423   return 0;
1424 #endif
1425 }
1426
1427 static int posix_mkdir(const char *pathname, mode_t mode)
1428 {
1429 #if defined(PLATFORM_WIN32)
1430   return mkdir(pathname);
1431 #else
1432   return mkdir(pathname, mode);
1433 #endif
1434 }
1435
1436 static boolean posix_process_running_setgid()
1437 {
1438 #if defined(PLATFORM_UNIX)
1439   return (getgid() != getegid());
1440 #else
1441   return FALSE;
1442 #endif
1443 }
1444
1445 void createDirectory(char *dir, char *text, int permission_class)
1446 {
1447   if (directoryExists(dir))
1448     return;
1449
1450   /* leave "other" permissions in umask untouched, but ensure group parts
1451      of USERDATA_DIR_MODE are not masked */
1452   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1453                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1454   mode_t last_umask = posix_umask(0);
1455   mode_t group_umask = ~(dir_mode & S_IRWXG);
1456   int running_setgid = posix_process_running_setgid();
1457
1458   if (permission_class == PERMS_PUBLIC)
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 = DIR_PERMS_PUBLIC_ALL;
1467   }
1468
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 = FILE_PERMS_PUBLIC_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