cleanup of some function names ('Ext' functions without 'base' functions)
[rocksndiamonds.git] / src / libgame / setup.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // setup.c
10 // ============================================================================
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <dirent.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <errno.h>
18
19 #include "platform.h"
20
21 #if !defined(PLATFORM_WIN32)
22 #include <pwd.h>
23 #include <sys/param.h>
24 #endif
25
26 #include "setup.h"
27 #include "joystick.h"
28 #include "text.h"
29 #include "misc.h"
30 #include "hash.h"
31
32
33 #define 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_AndroidGetExternalStorageState() &
1404                                   SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1405                                   SDL_AndroidGetExternalStoragePath() :
1406                                   SDL_AndroidGetInternalStoragePath());
1407 #else
1408   if (user_game_data_dir == NULL)
1409     user_game_data_dir = getPath2(getPersonalDataDir(),
1410                                   program.userdata_subdir);
1411 #endif
1412
1413   return user_game_data_dir;
1414 }
1415
1416 char *getSetupDir()
1417 {
1418   return getUserGameDataDir();
1419 }
1420
1421 static mode_t posix_umask(mode_t mask)
1422 {
1423 #if defined(PLATFORM_UNIX)
1424   return umask(mask);
1425 #else
1426   return 0;
1427 #endif
1428 }
1429
1430 static int posix_mkdir(const char *pathname, mode_t mode)
1431 {
1432 #if defined(PLATFORM_WIN32)
1433   return mkdir(pathname);
1434 #else
1435   return mkdir(pathname, mode);
1436 #endif
1437 }
1438
1439 static boolean posix_process_running_setgid()
1440 {
1441 #if defined(PLATFORM_UNIX)
1442   return (getgid() != getegid());
1443 #else
1444   return FALSE;
1445 #endif
1446 }
1447
1448 void createDirectory(char *dir, char *text, int permission_class)
1449 {
1450   if (directoryExists(dir))
1451     return;
1452
1453   /* leave "other" permissions in umask untouched, but ensure group parts
1454      of USERDATA_DIR_MODE are not masked */
1455   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1456                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1457   mode_t last_umask = posix_umask(0);
1458   mode_t group_umask = ~(dir_mode & S_IRWXG);
1459   int running_setgid = posix_process_running_setgid();
1460
1461   if (permission_class == PERMS_PUBLIC)
1462   {
1463     /* if we're setgid, protect files against "other" */
1464     /* else keep umask(0) to make the dir world-writable */
1465
1466     if (running_setgid)
1467       posix_umask(last_umask & group_umask);
1468     else
1469       dir_mode = DIR_PERMS_PUBLIC_ALL;
1470   }
1471
1472   if (posix_mkdir(dir, dir_mode) != 0)
1473     Error(ERR_WARN, "cannot create %s directory '%s': %s",
1474           text, dir, strerror(errno));
1475
1476   if (permission_class == PERMS_PUBLIC && !running_setgid)
1477     chmod(dir, dir_mode);
1478
1479   posix_umask(last_umask);              /* restore previous umask */
1480 }
1481
1482 void InitUserDataDirectory()
1483 {
1484   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1485 }
1486
1487 void SetFilePermissions(char *filename, int permission_class)
1488 {
1489   int running_setgid = posix_process_running_setgid();
1490   int perms = (permission_class == PERMS_PRIVATE ?
1491                FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1492
1493   if (permission_class == PERMS_PUBLIC && !running_setgid)
1494     perms = FILE_PERMS_PUBLIC_ALL;
1495
1496   chmod(filename, perms);
1497 }
1498
1499 char *getCookie(char *file_type)
1500 {
1501   static char cookie[MAX_COOKIE_LEN + 1];
1502
1503   if (strlen(program.cookie_prefix) + 1 +
1504       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1505     return "[COOKIE ERROR]";    /* should never happen */
1506
1507   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1508           program.cookie_prefix, file_type,
1509           program.version_major, program.version_minor);
1510
1511   return cookie;
1512 }
1513
1514 void fprintFileHeader(FILE *file, char *basename)
1515 {
1516   char *prefix = "# ";
1517   char *sep1 = "=";
1518
1519   fprintf_line_with_prefix(file, prefix, sep1, 77);
1520   fprintf(file, "%s%s\n", prefix, basename);
1521   fprintf_line_with_prefix(file, prefix, sep1, 77);
1522   fprintf(file, "\n");
1523 }
1524
1525 int getFileVersionFromCookieString(const char *cookie)
1526 {
1527   const char *ptr_cookie1, *ptr_cookie2;
1528   const char *pattern1 = "_FILE_VERSION_";
1529   const char *pattern2 = "?.?";
1530   const int len_cookie = strlen(cookie);
1531   const int len_pattern1 = strlen(pattern1);
1532   const int len_pattern2 = strlen(pattern2);
1533   const int len_pattern = len_pattern1 + len_pattern2;
1534   int version_major, version_minor;
1535
1536   if (len_cookie <= len_pattern)
1537     return -1;
1538
1539   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1540   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1541
1542   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1543     return -1;
1544
1545   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1546       ptr_cookie2[1] != '.' ||
1547       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1548     return -1;
1549
1550   version_major = ptr_cookie2[0] - '0';
1551   version_minor = ptr_cookie2[2] - '0';
1552
1553   return VERSION_IDENT(version_major, version_minor, 0, 0);
1554 }
1555
1556 boolean checkCookieString(const char *cookie, const char *template)
1557 {
1558   const char *pattern = "_FILE_VERSION_?.?";
1559   const int len_cookie = strlen(cookie);
1560   const int len_template = strlen(template);
1561   const int len_pattern = strlen(pattern);
1562
1563   if (len_cookie != len_template)
1564     return FALSE;
1565
1566   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1567     return FALSE;
1568
1569   return TRUE;
1570 }
1571
1572
1573 /* ------------------------------------------------------------------------- */
1574 /* setup file list and hash handling functions                               */
1575 /* ------------------------------------------------------------------------- */
1576
1577 char *getFormattedSetupEntry(char *token, char *value)
1578 {
1579   int i;
1580   static char entry[MAX_LINE_LEN];
1581
1582   /* if value is an empty string, just return token without value */
1583   if (*value == '\0')
1584     return token;
1585
1586   /* start with the token and some spaces to format output line */
1587   sprintf(entry, "%s:", token);
1588   for (i = strlen(entry); i < token_value_position; i++)
1589     strcat(entry, " ");
1590
1591   /* continue with the token's value */
1592   strcat(entry, value);
1593
1594   return entry;
1595 }
1596
1597 SetupFileList *newSetupFileList(char *token, char *value)
1598 {
1599   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1600
1601   new->token = getStringCopy(token);
1602   new->value = getStringCopy(value);
1603
1604   new->next = NULL;
1605
1606   return new;
1607 }
1608
1609 void freeSetupFileList(SetupFileList *list)
1610 {
1611   if (list == NULL)
1612     return;
1613
1614   checked_free(list->token);
1615   checked_free(list->value);
1616
1617   if (list->next)
1618     freeSetupFileList(list->next);
1619
1620   free(list);
1621 }
1622
1623 char *getListEntry(SetupFileList *list, char *token)
1624 {
1625   if (list == NULL)
1626     return NULL;
1627
1628   if (strEqual(list->token, token))
1629     return list->value;
1630   else
1631     return getListEntry(list->next, token);
1632 }
1633
1634 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1635 {
1636   if (list == NULL)
1637     return NULL;
1638
1639   if (strEqual(list->token, token))
1640   {
1641     checked_free(list->value);
1642
1643     list->value = getStringCopy(value);
1644
1645     return list;
1646   }
1647   else if (list->next == NULL)
1648     return (list->next = newSetupFileList(token, value));
1649   else
1650     return setListEntry(list->next, token, value);
1651 }
1652
1653 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1654 {
1655   if (list == NULL)
1656     return NULL;
1657
1658   if (list->next == NULL)
1659     return (list->next = newSetupFileList(token, value));
1660   else
1661     return addListEntry(list->next, token, value);
1662 }
1663
1664 #if ENABLE_UNUSED_CODE
1665 #ifdef DEBUG
1666 static void printSetupFileList(SetupFileList *list)
1667 {
1668   if (!list)
1669     return;
1670
1671   printf("token: '%s'\n", list->token);
1672   printf("value: '%s'\n", list->value);
1673
1674   printSetupFileList(list->next);
1675 }
1676 #endif
1677 #endif
1678
1679 #ifdef DEBUG
1680 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1681 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1682 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1683 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1684 #else
1685 #define insert_hash_entry hashtable_insert
1686 #define search_hash_entry hashtable_search
1687 #define change_hash_entry hashtable_change
1688 #define remove_hash_entry hashtable_remove
1689 #endif
1690
1691 unsigned int get_hash_from_key(void *key)
1692 {
1693   /*
1694     djb2
1695
1696     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1697     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1698     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1699     it works better than many other constants, prime or not) has never been
1700     adequately explained.
1701
1702     If you just want to have a good hash function, and cannot wait, djb2
1703     is one of the best string hash functions i know. It has excellent
1704     distribution and speed on many different sets of keys and table sizes.
1705     You are not likely to do better with one of the "well known" functions
1706     such as PJW, K&R, etc.
1707
1708     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1709   */
1710
1711   char *str = (char *)key;
1712   unsigned int hash = 5381;
1713   int c;
1714
1715   while ((c = *str++))
1716     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1717
1718   return hash;
1719 }
1720
1721 static int keys_are_equal(void *key1, void *key2)
1722 {
1723   return (strEqual((char *)key1, (char *)key2));
1724 }
1725
1726 SetupFileHash *newSetupFileHash()
1727 {
1728   SetupFileHash *new_hash =
1729     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1730
1731   if (new_hash == NULL)
1732     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1733
1734   return new_hash;
1735 }
1736
1737 void freeSetupFileHash(SetupFileHash *hash)
1738 {
1739   if (hash == NULL)
1740     return;
1741
1742   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1743 }
1744
1745 char *getHashEntry(SetupFileHash *hash, char *token)
1746 {
1747   if (hash == NULL)
1748     return NULL;
1749
1750   return search_hash_entry(hash, token);
1751 }
1752
1753 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1754 {
1755   char *value_copy;
1756
1757   if (hash == NULL)
1758     return;
1759
1760   value_copy = getStringCopy(value);
1761
1762   /* change value; if it does not exist, insert it as new */
1763   if (!change_hash_entry(hash, token, value_copy))
1764     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1765       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1766 }
1767
1768 char *removeHashEntry(SetupFileHash *hash, char *token)
1769 {
1770   if (hash == NULL)
1771     return NULL;
1772
1773   return remove_hash_entry(hash, token);
1774 }
1775
1776 #if ENABLE_UNUSED_CODE
1777 #if DEBUG
1778 static void printSetupFileHash(SetupFileHash *hash)
1779 {
1780   BEGIN_HASH_ITERATION(hash, itr)
1781   {
1782     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1783     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1784   }
1785   END_HASH_ITERATION(hash, itr)
1786 }
1787 #endif
1788 #endif
1789
1790 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE            1
1791 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING            0
1792 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH             0
1793
1794 static boolean token_value_separator_found = FALSE;
1795 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1796 static boolean token_value_separator_warning = FALSE;
1797 #endif
1798 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1799 static boolean token_already_exists_warning = FALSE;
1800 #endif
1801
1802 static boolean getTokenValueFromSetupLineExt(char *line,
1803                                              char **token_ptr, char **value_ptr,
1804                                              char *filename, char *line_raw,
1805                                              int line_nr,
1806                                              boolean separator_required)
1807 {
1808   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1809   char *token, *value, *line_ptr;
1810
1811   /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1812   if (line_raw == NULL)
1813   {
1814     strncpy(line_copy, line, MAX_LINE_LEN);
1815     line_copy[MAX_LINE_LEN] = '\0';
1816     line = line_copy;
1817
1818     strcpy(line_raw_copy, line_copy);
1819     line_raw = line_raw_copy;
1820   }
1821
1822   /* cut trailing comment from input line */
1823   for (line_ptr = line; *line_ptr; line_ptr++)
1824   {
1825     if (*line_ptr == '#')
1826     {
1827       *line_ptr = '\0';
1828       break;
1829     }
1830   }
1831
1832   /* cut trailing whitespaces from input line */
1833   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1834     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1835       *line_ptr = '\0';
1836
1837   /* ignore empty lines */
1838   if (*line == '\0')
1839     return FALSE;
1840
1841   /* cut leading whitespaces from token */
1842   for (token = line; *token; token++)
1843     if (*token != ' ' && *token != '\t')
1844       break;
1845
1846   /* start with empty value as reliable default */
1847   value = "";
1848
1849   token_value_separator_found = FALSE;
1850
1851   /* find end of token to determine start of value */
1852   for (line_ptr = token; *line_ptr; line_ptr++)
1853   {
1854     /* first look for an explicit token/value separator, like ':' or '=' */
1855     if (*line_ptr == ':' || *line_ptr == '=')
1856     {
1857       *line_ptr = '\0';                 /* terminate token string */
1858       value = line_ptr + 1;             /* set beginning of value */
1859
1860       token_value_separator_found = TRUE;
1861
1862       break;
1863     }
1864   }
1865
1866 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1867   /* fallback: if no token/value separator found, also allow whitespaces */
1868   if (!token_value_separator_found && !separator_required)
1869   {
1870     for (line_ptr = token; *line_ptr; line_ptr++)
1871     {
1872       if (*line_ptr == ' ' || *line_ptr == '\t')
1873       {
1874         *line_ptr = '\0';               /* terminate token string */
1875         value = line_ptr + 1;           /* set beginning of value */
1876
1877         token_value_separator_found = TRUE;
1878
1879         break;
1880       }
1881     }
1882
1883 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1884     if (token_value_separator_found)
1885     {
1886       if (!token_value_separator_warning)
1887       {
1888         Error(ERR_INFO_LINE, "-");
1889
1890         if (filename != NULL)
1891         {
1892           Error(ERR_WARN, "missing token/value separator(s) in config file:");
1893           Error(ERR_INFO, "- config file: '%s'", filename);
1894         }
1895         else
1896         {
1897           Error(ERR_WARN, "missing token/value separator(s):");
1898         }
1899
1900         token_value_separator_warning = TRUE;
1901       }
1902
1903       if (filename != NULL)
1904         Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1905       else
1906         Error(ERR_INFO, "- line: '%s'", line_raw);
1907     }
1908 #endif
1909   }
1910 #endif
1911
1912   /* cut trailing whitespaces from token */
1913   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1914     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1915       *line_ptr = '\0';
1916
1917   /* cut leading whitespaces from value */
1918   for (; *value; value++)
1919     if (*value != ' ' && *value != '\t')
1920       break;
1921
1922   *token_ptr = token;
1923   *value_ptr = value;
1924
1925   return TRUE;
1926 }
1927
1928 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1929 {
1930   /* while the internal (old) interface does not require a token/value
1931      separator (for downwards compatibility with existing files which
1932      don't use them), it is mandatory for the external (new) interface */
1933
1934   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1935 }
1936
1937 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1938                                  boolean top_recursion_level, boolean is_hash)
1939 {
1940   static SetupFileHash *include_filename_hash = NULL;
1941   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1942   char *token, *value, *line_ptr;
1943   void *insert_ptr = NULL;
1944   boolean read_continued_line = FALSE;
1945   File *file;
1946   int line_nr = 0, token_count = 0, include_count = 0;
1947
1948 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1949   token_value_separator_warning = FALSE;
1950 #endif
1951
1952 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1953   token_already_exists_warning = FALSE;
1954 #endif
1955
1956   if (!(file = openFile(filename, MODE_READ)))
1957   {
1958     Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
1959
1960     return FALSE;
1961   }
1962
1963   /* use "insert pointer" to store list end for constant insertion complexity */
1964   if (!is_hash)
1965     insert_ptr = setup_file_data;
1966
1967   /* on top invocation, create hash to mark included files (to prevent loops) */
1968   if (top_recursion_level)
1969     include_filename_hash = newSetupFileHash();
1970
1971   /* mark this file as already included (to prevent including it again) */
1972   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1973
1974   while (!checkEndOfFile(file))
1975   {
1976     /* read next line of input file */
1977     if (!getStringFromFile(file, line, MAX_LINE_LEN))
1978       break;
1979
1980     /* check if line was completely read and is terminated by line break */
1981     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1982       line_nr++;
1983
1984     /* cut trailing line break (this can be newline and/or carriage return) */
1985     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1986       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1987         *line_ptr = '\0';
1988
1989     /* copy raw input line for later use (mainly debugging output) */
1990     strcpy(line_raw, line);
1991
1992     if (read_continued_line)
1993     {
1994       /* append new line to existing line, if there is enough space */
1995       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1996         strcat(previous_line, line_ptr);
1997
1998       strcpy(line, previous_line);      /* copy storage buffer to line */
1999
2000       read_continued_line = FALSE;
2001     }
2002
2003     /* if the last character is '\', continue at next line */
2004     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2005     {
2006       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
2007       strcpy(previous_line, line);      /* copy line to storage buffer */
2008
2009       read_continued_line = TRUE;
2010
2011       continue;
2012     }
2013
2014     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2015                                        line_raw, line_nr, FALSE))
2016       continue;
2017
2018     if (*token)
2019     {
2020       if (strEqual(token, "include"))
2021       {
2022         if (getHashEntry(include_filename_hash, value) == NULL)
2023         {
2024           char *basepath = getBasePath(filename);
2025           char *basename = getBaseName(value);
2026           char *filename_include = getPath2(basepath, basename);
2027
2028           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2029
2030           free(basepath);
2031           free(basename);
2032           free(filename_include);
2033
2034           include_count++;
2035         }
2036         else
2037         {
2038           Error(ERR_WARN, "ignoring already processed file '%s'", value);
2039         }
2040       }
2041       else
2042       {
2043         if (is_hash)
2044         {
2045 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2046           char *old_value =
2047             getHashEntry((SetupFileHash *)setup_file_data, token);
2048
2049           if (old_value != NULL)
2050           {
2051             if (!token_already_exists_warning)
2052             {
2053               Error(ERR_INFO_LINE, "-");
2054               Error(ERR_WARN, "duplicate token(s) found in config file:");
2055               Error(ERR_INFO, "- config file: '%s'", filename);
2056
2057               token_already_exists_warning = TRUE;
2058             }
2059
2060             Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2061             Error(ERR_INFO, "  old value: '%s'", old_value);
2062             Error(ERR_INFO, "  new value: '%s'", value);
2063           }
2064 #endif
2065
2066           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2067         }
2068         else
2069         {
2070           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2071         }
2072
2073         token_count++;
2074       }
2075     }
2076   }
2077
2078   closeFile(file);
2079
2080 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2081   if (token_value_separator_warning)
2082     Error(ERR_INFO_LINE, "-");
2083 #endif
2084
2085 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2086   if (token_already_exists_warning)
2087     Error(ERR_INFO_LINE, "-");
2088 #endif
2089
2090   if (token_count == 0 && include_count == 0)
2091     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2092
2093   if (top_recursion_level)
2094     freeSetupFileHash(include_filename_hash);
2095
2096   return TRUE;
2097 }
2098
2099 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2100 {
2101   FILE *file;
2102
2103   if (!(file = fopen(filename, MODE_WRITE)))
2104   {
2105     Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2106
2107     return;
2108   }
2109
2110   BEGIN_HASH_ITERATION(hash, itr)
2111   {
2112     fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2113                                                  HASH_ITERATION_VALUE(itr)));
2114   }
2115   END_HASH_ITERATION(hash, itr)
2116
2117   fclose(file);
2118 }
2119
2120 SetupFileList *loadSetupFileList(char *filename)
2121 {
2122   SetupFileList *setup_file_list = newSetupFileList("", "");
2123   SetupFileList *first_valid_list_entry;
2124
2125   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2126   {
2127     freeSetupFileList(setup_file_list);
2128
2129     return NULL;
2130   }
2131
2132   first_valid_list_entry = setup_file_list->next;
2133
2134   /* free empty list header */
2135   setup_file_list->next = NULL;
2136   freeSetupFileList(setup_file_list);
2137
2138   return first_valid_list_entry;
2139 }
2140
2141 SetupFileHash *loadSetupFileHash(char *filename)
2142 {
2143   SetupFileHash *setup_file_hash = newSetupFileHash();
2144
2145   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2146   {
2147     freeSetupFileHash(setup_file_hash);
2148
2149     return NULL;
2150   }
2151
2152   return setup_file_hash;
2153 }
2154
2155
2156 /* ========================================================================= */
2157 /* setup file stuff                                                          */
2158 /* ========================================================================= */
2159
2160 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
2161 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
2162 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
2163
2164 /* level directory info */
2165 #define LEVELINFO_TOKEN_IDENTIFIER              0
2166 #define LEVELINFO_TOKEN_NAME                    1
2167 #define LEVELINFO_TOKEN_NAME_SORTING            2
2168 #define LEVELINFO_TOKEN_AUTHOR                  3
2169 #define LEVELINFO_TOKEN_YEAR                    4
2170 #define LEVELINFO_TOKEN_IMPORTED_FROM           5
2171 #define LEVELINFO_TOKEN_IMPORTED_BY             6
2172 #define LEVELINFO_TOKEN_TESTED_BY               7
2173 #define LEVELINFO_TOKEN_LEVELS                  8
2174 #define LEVELINFO_TOKEN_FIRST_LEVEL             9
2175 #define LEVELINFO_TOKEN_SORT_PRIORITY           10
2176 #define LEVELINFO_TOKEN_LATEST_ENGINE           11
2177 #define LEVELINFO_TOKEN_LEVEL_GROUP             12
2178 #define LEVELINFO_TOKEN_READONLY                13
2179 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        14
2180 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        15
2181 #define LEVELINFO_TOKEN_GRAPHICS_SET            16
2182 #define LEVELINFO_TOKEN_SOUNDS_SET              17
2183 #define LEVELINFO_TOKEN_MUSIC_SET               18
2184 #define LEVELINFO_TOKEN_FILENAME                19
2185 #define LEVELINFO_TOKEN_FILETYPE                20
2186 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           21
2187 #define LEVELINFO_TOKEN_HANDICAP                22
2188 #define LEVELINFO_TOKEN_SKIP_LEVELS             23
2189
2190 #define NUM_LEVELINFO_TOKENS                    24
2191
2192 static LevelDirTree ldi;
2193
2194 static struct TokenInfo levelinfo_tokens[] =
2195 {
2196   /* level directory info */
2197   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2198   { TYPE_STRING,        &ldi.name,              "name"                  },
2199   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2200   { TYPE_STRING,        &ldi.author,            "author"                },
2201   { TYPE_STRING,        &ldi.year,              "year"                  },
2202   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2203   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2204   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2205   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2206   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2207   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2208   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2209   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2210   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2211   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2212   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2213   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2214   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2215   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2216   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2217   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2218   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2219   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2220   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           }
2221 };
2222
2223 static struct TokenInfo artworkinfo_tokens[] =
2224 {
2225   /* artwork directory info */
2226   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2227   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2228   { TYPE_STRING,        &ldi.name,              "name"                  },
2229   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2230   { TYPE_STRING,        &ldi.author,            "author"                },
2231   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2232   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2233   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2234   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2235   { TYPE_INTEGER,       &ldi.color,             "color"                 },
2236   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2237
2238   { -1,                 NULL,                   NULL                    },
2239 };
2240
2241 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2242 {
2243   ti->type = type;
2244
2245   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2246                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2247                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2248                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2249                   NULL);
2250
2251   ti->node_parent = NULL;
2252   ti->node_group = NULL;
2253   ti->next = NULL;
2254
2255   ti->cl_first = -1;
2256   ti->cl_cursor = -1;
2257
2258   ti->subdir = NULL;
2259   ti->fullpath = NULL;
2260   ti->basepath = NULL;
2261   ti->identifier = NULL;
2262   ti->name = getStringCopy(ANONYMOUS_NAME);
2263   ti->name_sorting = NULL;
2264   ti->author = getStringCopy(ANONYMOUS_NAME);
2265   ti->year = NULL;
2266
2267   ti->sort_priority = LEVELCLASS_UNDEFINED;     /* default: least priority */
2268   ti->latest_engine = FALSE;                    /* default: get from level */
2269   ti->parent_link = FALSE;
2270   ti->in_user_dir = FALSE;
2271   ti->user_defined = FALSE;
2272   ti->color = 0;
2273   ti->class_desc = NULL;
2274
2275   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2276
2277   if (ti->type == TREE_TYPE_LEVEL_DIR)
2278   {
2279     ti->imported_from = NULL;
2280     ti->imported_by = NULL;
2281     ti->tested_by = NULL;
2282
2283     ti->graphics_set_ecs = NULL;
2284     ti->graphics_set_aga = NULL;
2285     ti->graphics_set = NULL;
2286     ti->sounds_set = NULL;
2287     ti->music_set = NULL;
2288     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2289     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2290     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2291
2292     ti->level_filename = NULL;
2293     ti->level_filetype = NULL;
2294
2295     ti->special_flags = NULL;
2296
2297     ti->levels = 0;
2298     ti->first_level = 0;
2299     ti->last_level = 0;
2300     ti->level_group = FALSE;
2301     ti->handicap_level = 0;
2302     ti->readonly = TRUE;
2303     ti->handicap = TRUE;
2304     ti->skip_levels = FALSE;
2305   }
2306 }
2307
2308 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2309 {
2310   if (parent == NULL)
2311   {
2312     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2313
2314     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2315
2316     return;
2317   }
2318
2319   /* copy all values from the parent structure */
2320
2321   ti->type = parent->type;
2322
2323   ti->node_top = parent->node_top;
2324   ti->node_parent = parent;
2325   ti->node_group = NULL;
2326   ti->next = NULL;
2327
2328   ti->cl_first = -1;
2329   ti->cl_cursor = -1;
2330
2331   ti->subdir = NULL;
2332   ti->fullpath = NULL;
2333   ti->basepath = NULL;
2334   ti->identifier = NULL;
2335   ti->name = getStringCopy(ANONYMOUS_NAME);
2336   ti->name_sorting = NULL;
2337   ti->author = getStringCopy(parent->author);
2338   ti->year = getStringCopy(parent->year);
2339
2340   ti->sort_priority = parent->sort_priority;
2341   ti->latest_engine = parent->latest_engine;
2342   ti->parent_link = FALSE;
2343   ti->in_user_dir = parent->in_user_dir;
2344   ti->user_defined = parent->user_defined;
2345   ti->color = parent->color;
2346   ti->class_desc = getStringCopy(parent->class_desc);
2347
2348   ti->infotext = getStringCopy(parent->infotext);
2349
2350   if (ti->type == TREE_TYPE_LEVEL_DIR)
2351   {
2352     ti->imported_from = getStringCopy(parent->imported_from);
2353     ti->imported_by = getStringCopy(parent->imported_by);
2354     ti->tested_by = getStringCopy(parent->tested_by);
2355
2356     ti->graphics_set_ecs = NULL;
2357     ti->graphics_set_aga = NULL;
2358     ti->graphics_set = NULL;
2359     ti->sounds_set = NULL;
2360     ti->music_set = NULL;
2361     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2362     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2363     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2364
2365     ti->level_filename = NULL;
2366     ti->level_filetype = NULL;
2367
2368     ti->special_flags = getStringCopy(parent->special_flags);
2369
2370     ti->levels = 0;
2371     ti->first_level = 0;
2372     ti->last_level = 0;
2373     ti->level_group = FALSE;
2374     ti->handicap_level = 0;
2375     ti->readonly = parent->readonly;
2376     ti->handicap = TRUE;
2377     ti->skip_levels = FALSE;
2378   }
2379 }
2380
2381 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2382 {
2383   TreeInfo *ti_copy = newTreeInfo();
2384
2385   /* copy all values from the original structure */
2386
2387   ti_copy->type                 = ti->type;
2388
2389   ti_copy->node_top             = ti->node_top;
2390   ti_copy->node_parent          = ti->node_parent;
2391   ti_copy->node_group           = ti->node_group;
2392   ti_copy->next                 = ti->next;
2393
2394   ti_copy->cl_first             = ti->cl_first;
2395   ti_copy->cl_cursor            = ti->cl_cursor;
2396
2397   ti_copy->subdir               = getStringCopy(ti->subdir);
2398   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2399   ti_copy->basepath             = getStringCopy(ti->basepath);
2400   ti_copy->identifier           = getStringCopy(ti->identifier);
2401   ti_copy->name                 = getStringCopy(ti->name);
2402   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2403   ti_copy->author               = getStringCopy(ti->author);
2404   ti_copy->year                 = getStringCopy(ti->year);
2405   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2406   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2407   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2408
2409   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2410   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2411   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2412   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2413   ti_copy->music_set            = getStringCopy(ti->music_set);
2414   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2415   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2416   ti_copy->music_path           = getStringCopy(ti->music_path);
2417
2418   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2419   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2420
2421   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2422
2423   ti_copy->levels               = ti->levels;
2424   ti_copy->first_level          = ti->first_level;
2425   ti_copy->last_level           = ti->last_level;
2426   ti_copy->sort_priority        = ti->sort_priority;
2427
2428   ti_copy->latest_engine        = ti->latest_engine;
2429
2430   ti_copy->level_group          = ti->level_group;
2431   ti_copy->parent_link          = ti->parent_link;
2432   ti_copy->in_user_dir          = ti->in_user_dir;
2433   ti_copy->user_defined         = ti->user_defined;
2434   ti_copy->readonly             = ti->readonly;
2435   ti_copy->handicap             = ti->handicap;
2436   ti_copy->skip_levels          = ti->skip_levels;
2437
2438   ti_copy->color                = ti->color;
2439   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2440   ti_copy->handicap_level       = ti->handicap_level;
2441
2442   ti_copy->infotext             = getStringCopy(ti->infotext);
2443
2444   return ti_copy;
2445 }
2446
2447 void freeTreeInfo(TreeInfo *ti)
2448 {
2449   if (ti == NULL)
2450     return;
2451
2452   checked_free(ti->subdir);
2453   checked_free(ti->fullpath);
2454   checked_free(ti->basepath);
2455   checked_free(ti->identifier);
2456
2457   checked_free(ti->name);
2458   checked_free(ti->name_sorting);
2459   checked_free(ti->author);
2460   checked_free(ti->year);
2461
2462   checked_free(ti->class_desc);
2463
2464   checked_free(ti->infotext);
2465
2466   if (ti->type == TREE_TYPE_LEVEL_DIR)
2467   {
2468     checked_free(ti->imported_from);
2469     checked_free(ti->imported_by);
2470     checked_free(ti->tested_by);
2471
2472     checked_free(ti->graphics_set_ecs);
2473     checked_free(ti->graphics_set_aga);
2474     checked_free(ti->graphics_set);
2475     checked_free(ti->sounds_set);
2476     checked_free(ti->music_set);
2477
2478     checked_free(ti->graphics_path);
2479     checked_free(ti->sounds_path);
2480     checked_free(ti->music_path);
2481
2482     checked_free(ti->level_filename);
2483     checked_free(ti->level_filetype);
2484
2485     checked_free(ti->special_flags);
2486   }
2487
2488   // recursively free child node
2489   if (ti->node_group)
2490     freeTreeInfo(ti->node_group);
2491
2492   // recursively free next node
2493   if (ti->next)
2494     freeTreeInfo(ti->next);
2495
2496   checked_free(ti);
2497 }
2498
2499 void setSetupInfo(struct TokenInfo *token_info,
2500                   int token_nr, char *token_value)
2501 {
2502   int token_type = token_info[token_nr].type;
2503   void *setup_value = token_info[token_nr].value;
2504
2505   if (token_value == NULL)
2506     return;
2507
2508   /* set setup field to corresponding token value */
2509   switch (token_type)
2510   {
2511     case TYPE_BOOLEAN:
2512     case TYPE_SWITCH:
2513       *(boolean *)setup_value = get_boolean_from_string(token_value);
2514       break;
2515
2516     case TYPE_SWITCH3:
2517       *(int *)setup_value = get_switch3_from_string(token_value);
2518       break;
2519
2520     case TYPE_KEY:
2521       *(Key *)setup_value = getKeyFromKeyName(token_value);
2522       break;
2523
2524     case TYPE_KEY_X11:
2525       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2526       break;
2527
2528     case TYPE_INTEGER:
2529       *(int *)setup_value = get_integer_from_string(token_value);
2530       break;
2531
2532     case TYPE_STRING:
2533       checked_free(*(char **)setup_value);
2534       *(char **)setup_value = getStringCopy(token_value);
2535       break;
2536
2537     default:
2538       break;
2539   }
2540 }
2541
2542 static int compareTreeInfoEntries(const void *object1, const void *object2)
2543 {
2544   const TreeInfo *entry1 = *((TreeInfo **)object1);
2545   const TreeInfo *entry2 = *((TreeInfo **)object2);
2546   int class_sorting1 = 0, class_sorting2 = 0;
2547   int compare_result;
2548
2549   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2550   {
2551     class_sorting1 = LEVELSORTING(entry1);
2552     class_sorting2 = LEVELSORTING(entry2);
2553   }
2554   else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2555            entry1->type == TREE_TYPE_SOUNDS_DIR ||
2556            entry1->type == TREE_TYPE_MUSIC_DIR)
2557   {
2558     class_sorting1 = ARTWORKSORTING(entry1);
2559     class_sorting2 = ARTWORKSORTING(entry2);
2560   }
2561
2562   if (entry1->parent_link || entry2->parent_link)
2563     compare_result = (entry1->parent_link ? -1 : +1);
2564   else if (entry1->sort_priority == entry2->sort_priority)
2565   {
2566     char *name1 = getStringToLower(entry1->name_sorting);
2567     char *name2 = getStringToLower(entry2->name_sorting);
2568
2569     compare_result = strcmp(name1, name2);
2570
2571     free(name1);
2572     free(name2);
2573   }
2574   else if (class_sorting1 == class_sorting2)
2575     compare_result = entry1->sort_priority - entry2->sort_priority;
2576   else
2577     compare_result = class_sorting1 - class_sorting2;
2578
2579   return compare_result;
2580 }
2581
2582 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2583 {
2584   TreeInfo *ti_new;
2585
2586   if (node_parent == NULL)
2587     return NULL;
2588
2589   ti_new = newTreeInfo();
2590   setTreeInfoToDefaults(ti_new, node_parent->type);
2591
2592   ti_new->node_parent = node_parent;
2593   ti_new->parent_link = TRUE;
2594
2595   setString(&ti_new->identifier, node_parent->identifier);
2596   setString(&ti_new->name, ".. (parent directory)");
2597   setString(&ti_new->name_sorting, ti_new->name);
2598
2599   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2600   setString(&ti_new->fullpath, node_parent->fullpath);
2601
2602   ti_new->sort_priority = node_parent->sort_priority;
2603   ti_new->latest_engine = node_parent->latest_engine;
2604
2605   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2606
2607   pushTreeInfo(&node_parent->node_group, ti_new);
2608
2609   return ti_new;
2610 }
2611
2612 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2613 {
2614   TreeInfo *ti_new, *ti_new2;
2615
2616   if (node_first == NULL)
2617     return NULL;
2618
2619   ti_new = newTreeInfo();
2620   setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2621
2622   ti_new->node_parent = NULL;
2623   ti_new->parent_link = FALSE;
2624
2625   setString(&ti_new->identifier, node_first->identifier);
2626   setString(&ti_new->name, "level sets");
2627   setString(&ti_new->name_sorting, ti_new->name);
2628
2629   setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2630   setString(&ti_new->fullpath, node_first->fullpath);
2631
2632   ti_new->sort_priority = node_first->sort_priority;;
2633   ti_new->latest_engine = node_first->latest_engine;
2634
2635   setString(&ti_new->class_desc, "level sets");
2636
2637   ti_new->node_group = node_first;
2638   ti_new->level_group = TRUE;
2639
2640   ti_new2 = createParentTreeInfoNode(ti_new);
2641
2642   setString(&ti_new2->name, ".. (main menu)");
2643   setString(&ti_new2->name_sorting, ti_new2->name);
2644
2645   return ti_new;
2646 }
2647
2648
2649 /* -------------------------------------------------------------------------- */
2650 /* functions for handling level and custom artwork info cache                 */
2651 /* -------------------------------------------------------------------------- */
2652
2653 static void LoadArtworkInfoCache()
2654 {
2655   InitCacheDirectory();
2656
2657   if (artworkinfo_cache_old == NULL)
2658   {
2659     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2660
2661     /* try to load artwork info hash from already existing cache file */
2662     artworkinfo_cache_old = loadSetupFileHash(filename);
2663
2664     /* if no artwork info cache file was found, start with empty hash */
2665     if (artworkinfo_cache_old == NULL)
2666       artworkinfo_cache_old = newSetupFileHash();
2667
2668     free(filename);
2669   }
2670
2671   if (artworkinfo_cache_new == NULL)
2672     artworkinfo_cache_new = newSetupFileHash();
2673 }
2674
2675 static void SaveArtworkInfoCache()
2676 {
2677   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2678
2679   InitCacheDirectory();
2680
2681   saveSetupFileHash(artworkinfo_cache_new, filename);
2682
2683   free(filename);
2684 }
2685
2686 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2687 {
2688   static char *prefix = NULL;
2689
2690   checked_free(prefix);
2691
2692   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2693
2694   return prefix;
2695 }
2696
2697 /* (identical to above function, but separate string buffer needed -- nasty) */
2698 static char *getCacheToken(char *prefix, char *suffix)
2699 {
2700   static char *token = NULL;
2701
2702   checked_free(token);
2703
2704   token = getStringCat2WithSeparator(prefix, suffix, ".");
2705
2706   return token;
2707 }
2708
2709 static char *getFileTimestampString(char *filename)
2710 {
2711   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2712 }
2713
2714 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2715 {
2716   struct stat file_status;
2717
2718   if (timestamp_string == NULL)
2719     return TRUE;
2720
2721   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2722     return TRUE;
2723
2724   return (file_status.st_mtime != atoi(timestamp_string));
2725 }
2726
2727 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2728 {
2729   char *identifier = level_node->subdir;
2730   char *type_string = ARTWORK_DIRECTORY(type);
2731   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2732   char *token_main = getCacheToken(token_prefix, "CACHED");
2733   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2734   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2735   TreeInfo *artwork_info = NULL;
2736
2737   if (!use_artworkinfo_cache)
2738     return NULL;
2739
2740   if (cached)
2741   {
2742     int i;
2743
2744     artwork_info = newTreeInfo();
2745     setTreeInfoToDefaults(artwork_info, type);
2746
2747     /* set all structure fields according to the token/value pairs */
2748     ldi = *artwork_info;
2749     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2750     {
2751       char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2752       char *value = getHashEntry(artworkinfo_cache_old, token);
2753
2754       setSetupInfo(artworkinfo_tokens, i, value);
2755
2756       /* check if cache entry for this item is invalid or incomplete */
2757       if (value == NULL)
2758       {
2759         Error(ERR_WARN, "cache entry '%s' invalid", token);
2760
2761         cached = FALSE;
2762       }
2763     }
2764
2765     *artwork_info = ldi;
2766   }
2767
2768   if (cached)
2769   {
2770     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2771                                         LEVELINFO_FILENAME);
2772     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2773                                           ARTWORKINFO_FILENAME(type));
2774
2775     /* check if corresponding "levelinfo.conf" file has changed */
2776     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2777     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2778
2779     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2780       cached = FALSE;
2781
2782     /* check if corresponding "<artworkinfo>.conf" file has changed */
2783     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2784     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2785
2786     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2787       cached = FALSE;
2788
2789     checked_free(filename_levelinfo);
2790     checked_free(filename_artworkinfo);
2791   }
2792
2793   if (!cached && artwork_info != NULL)
2794   {
2795     freeTreeInfo(artwork_info);
2796
2797     return NULL;
2798   }
2799
2800   return artwork_info;
2801 }
2802
2803 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2804                                      LevelDirTree *level_node, int type)
2805 {
2806   char *identifier = level_node->subdir;
2807   char *type_string = ARTWORK_DIRECTORY(type);
2808   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2809   char *token_main = getCacheToken(token_prefix, "CACHED");
2810   boolean set_cache_timestamps = TRUE;
2811   int i;
2812
2813   setHashEntry(artworkinfo_cache_new, token_main, "true");
2814
2815   if (set_cache_timestamps)
2816   {
2817     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2818                                         LEVELINFO_FILENAME);
2819     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2820                                           ARTWORKINFO_FILENAME(type));
2821     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2822     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2823
2824     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2825     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2826
2827     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2828     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2829
2830     checked_free(filename_levelinfo);
2831     checked_free(filename_artworkinfo);
2832     checked_free(timestamp_levelinfo);
2833     checked_free(timestamp_artworkinfo);
2834   }
2835
2836   ldi = *artwork_info;
2837   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2838   {
2839     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2840     char *value = getSetupValue(artworkinfo_tokens[i].type,
2841                                 artworkinfo_tokens[i].value);
2842     if (value != NULL)
2843       setHashEntry(artworkinfo_cache_new, token, value);
2844   }
2845 }
2846
2847
2848 /* -------------------------------------------------------------------------- */
2849 /* functions for loading level info and custom artwork info                   */
2850 /* -------------------------------------------------------------------------- */
2851
2852 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2853 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2854
2855 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2856                                           TreeInfo *node_parent,
2857                                           char *level_directory,
2858                                           char *directory_name)
2859 {
2860   char *directory_path = getPath2(level_directory, directory_name);
2861   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2862   SetupFileHash *setup_file_hash;
2863   LevelDirTree *leveldir_new = NULL;
2864   int i;
2865
2866   /* unless debugging, silently ignore directories without "levelinfo.conf" */
2867   if (!options.debug && !fileExists(filename))
2868   {
2869     free(directory_path);
2870     free(filename);
2871
2872     return FALSE;
2873   }
2874
2875   setup_file_hash = loadSetupFileHash(filename);
2876
2877   if (setup_file_hash == NULL)
2878   {
2879     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2880
2881     free(directory_path);
2882     free(filename);
2883
2884     return FALSE;
2885   }
2886
2887   leveldir_new = newTreeInfo();
2888
2889   if (node_parent)
2890     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2891   else
2892     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2893
2894   leveldir_new->subdir = getStringCopy(directory_name);
2895
2896   /* set all structure fields according to the token/value pairs */
2897   ldi = *leveldir_new;
2898   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2899     setSetupInfo(levelinfo_tokens, i,
2900                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2901   *leveldir_new = ldi;
2902
2903   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2904     setString(&leveldir_new->name, leveldir_new->subdir);
2905
2906   if (leveldir_new->identifier == NULL)
2907     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2908
2909   if (leveldir_new->name_sorting == NULL)
2910     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2911
2912   if (node_parent == NULL)              /* top level group */
2913   {
2914     leveldir_new->basepath = getStringCopy(level_directory);
2915     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2916   }
2917   else                                  /* sub level group */
2918   {
2919     leveldir_new->basepath = getStringCopy(node_parent->basepath);
2920     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2921   }
2922
2923   leveldir_new->last_level =
2924     leveldir_new->first_level + leveldir_new->levels - 1;
2925
2926   leveldir_new->in_user_dir =
2927     (!strEqual(leveldir_new->basepath, options.level_directory));
2928
2929   /* adjust some settings if user's private level directory was detected */
2930   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2931       leveldir_new->in_user_dir &&
2932       (strEqual(leveldir_new->subdir, getLoginName()) ||
2933        strEqual(leveldir_new->name,   getLoginName()) ||
2934        strEqual(leveldir_new->author, getRealName())))
2935   {
2936     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2937     leveldir_new->readonly = FALSE;
2938   }
2939
2940   leveldir_new->user_defined =
2941     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2942
2943   leveldir_new->color = LEVELCOLOR(leveldir_new);
2944
2945   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2946
2947   leveldir_new->handicap_level =        /* set handicap to default value */
2948     (leveldir_new->user_defined || !leveldir_new->handicap ?
2949      leveldir_new->last_level : leveldir_new->first_level);
2950
2951   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2952
2953   pushTreeInfo(node_first, leveldir_new);
2954
2955   freeSetupFileHash(setup_file_hash);
2956
2957   if (leveldir_new->level_group)
2958   {
2959     /* create node to link back to current level directory */
2960     createParentTreeInfoNode(leveldir_new);
2961
2962     /* recursively step into sub-directory and look for more level series */
2963     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2964                               leveldir_new, directory_path);
2965   }
2966
2967   free(directory_path);
2968   free(filename);
2969
2970   return TRUE;
2971 }
2972
2973 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2974                                       TreeInfo *node_parent,
2975                                       char *level_directory)
2976 {
2977   Directory *dir;
2978   DirectoryEntry *dir_entry;
2979   boolean valid_entry_found = FALSE;
2980
2981   if ((dir = openDirectory(level_directory)) == NULL)
2982   {
2983     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2984
2985     return;
2986   }
2987
2988   while ((dir_entry = readDirectory(dir)) != NULL)      /* loop all entries */
2989   {
2990     char *directory_name = dir_entry->basename;
2991     char *directory_path = getPath2(level_directory, directory_name);
2992
2993     /* skip entries for current and parent directory */
2994     if (strEqual(directory_name, ".") ||
2995         strEqual(directory_name, ".."))
2996     {
2997       free(directory_path);
2998
2999       continue;
3000     }
3001
3002     /* find out if directory entry is itself a directory */
3003     if (!dir_entry->is_directory)                       /* not a directory */
3004     {
3005       free(directory_path);
3006
3007       continue;
3008     }
3009
3010     free(directory_path);
3011
3012     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3013         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3014         strEqual(directory_name, MUSIC_DIRECTORY))
3015       continue;
3016
3017     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3018                                                     level_directory,
3019                                                     directory_name);
3020   }
3021
3022   closeDirectory(dir);
3023
3024   /* special case: top level directory may directly contain "levelinfo.conf" */
3025   if (node_parent == NULL && !valid_entry_found)
3026   {
3027     /* check if this directory directly contains a file "levelinfo.conf" */
3028     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3029                                                     level_directory, ".");
3030   }
3031
3032   if (!valid_entry_found)
3033     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3034           level_directory);
3035 }
3036
3037 boolean AdjustGraphicsForEMC()
3038 {
3039   boolean settings_changed = FALSE;
3040
3041   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3042   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3043
3044   return settings_changed;
3045 }
3046
3047 void LoadLevelInfo()
3048 {
3049   InitUserLevelDirectory(getLoginName());
3050
3051   DrawInitText("Loading level series", 120, FC_GREEN);
3052
3053   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3054   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3055
3056   leveldir_first = createTopTreeInfoNode(leveldir_first);
3057
3058   /* after loading all level set information, clone the level directory tree
3059      and remove all level sets without levels (these may still contain artwork
3060      to be offered in the setup menu as "custom artwork", and are therefore
3061      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3062   leveldir_first_all = leveldir_first;
3063   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3064
3065   AdjustGraphicsForEMC();
3066
3067   /* before sorting, the first entries will be from the user directory */
3068   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3069
3070   if (leveldir_first == NULL)
3071     Error(ERR_EXIT, "cannot find any valid level series in any directory");
3072
3073   sortTreeInfo(&leveldir_first);
3074
3075 #if ENABLE_UNUSED_CODE
3076   dumpTreeInfo(leveldir_first, 0);
3077 #endif
3078 }
3079
3080 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3081                                               TreeInfo *node_parent,
3082                                               char *base_directory,
3083                                               char *directory_name, int type)
3084 {
3085   char *directory_path = getPath2(base_directory, directory_name);
3086   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3087   SetupFileHash *setup_file_hash = NULL;
3088   TreeInfo *artwork_new = NULL;
3089   int i;
3090
3091   if (fileExists(filename))
3092     setup_file_hash = loadSetupFileHash(filename);
3093
3094   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
3095   {
3096     Directory *dir;
3097     DirectoryEntry *dir_entry;
3098     boolean valid_file_found = FALSE;
3099
3100     if ((dir = openDirectory(directory_path)) != NULL)
3101     {
3102       while ((dir_entry = readDirectory(dir)) != NULL)
3103       {
3104         if (FileIsArtworkType(dir_entry->filename, type))
3105         {
3106           valid_file_found = TRUE;
3107
3108           break;
3109         }
3110       }
3111
3112       closeDirectory(dir);
3113     }
3114
3115     if (!valid_file_found)
3116     {
3117       if (!strEqual(directory_name, "."))
3118         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3119
3120       free(directory_path);
3121       free(filename);
3122
3123       return FALSE;
3124     }
3125   }
3126
3127   artwork_new = newTreeInfo();
3128
3129   if (node_parent)
3130     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3131   else
3132     setTreeInfoToDefaults(artwork_new, type);
3133
3134   artwork_new->subdir = getStringCopy(directory_name);
3135
3136   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
3137   {
3138     /* set all structure fields according to the token/value pairs */
3139     ldi = *artwork_new;
3140     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3141       setSetupInfo(levelinfo_tokens, i,
3142                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3143     *artwork_new = ldi;
3144
3145     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3146       setString(&artwork_new->name, artwork_new->subdir);
3147
3148     if (artwork_new->identifier == NULL)
3149       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3150
3151     if (artwork_new->name_sorting == NULL)
3152       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3153   }
3154
3155   if (node_parent == NULL)              /* top level group */
3156   {
3157     artwork_new->basepath = getStringCopy(base_directory);
3158     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3159   }
3160   else                                  /* sub level group */
3161   {
3162     artwork_new->basepath = getStringCopy(node_parent->basepath);
3163     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3164   }
3165
3166   artwork_new->in_user_dir =
3167     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3168
3169   /* (may use ".sort_priority" from "setup_file_hash" above) */
3170   artwork_new->color = ARTWORKCOLOR(artwork_new);
3171
3172   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3173
3174   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
3175   {
3176     if (strEqual(artwork_new->subdir, "."))
3177     {
3178       if (artwork_new->user_defined)
3179       {
3180         setString(&artwork_new->identifier, "private");
3181         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3182       }
3183       else
3184       {
3185         setString(&artwork_new->identifier, "classic");
3186         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3187       }
3188
3189       /* set to new values after changing ".sort_priority" */
3190       artwork_new->color = ARTWORKCOLOR(artwork_new);
3191
3192       setString(&artwork_new->class_desc,
3193                 getLevelClassDescription(artwork_new));
3194     }
3195     else
3196     {
3197       setString(&artwork_new->identifier, artwork_new->subdir);
3198     }
3199
3200     setString(&artwork_new->name, artwork_new->identifier);
3201     setString(&artwork_new->name_sorting, artwork_new->name);
3202   }
3203
3204   pushTreeInfo(node_first, artwork_new);
3205
3206   freeSetupFileHash(setup_file_hash);
3207
3208   free(directory_path);
3209   free(filename);
3210
3211   return TRUE;
3212 }
3213
3214 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3215                                           TreeInfo *node_parent,
3216                                           char *base_directory, int type)
3217 {
3218   Directory *dir;
3219   DirectoryEntry *dir_entry;
3220   boolean valid_entry_found = FALSE;
3221
3222   if ((dir = openDirectory(base_directory)) == NULL)
3223   {
3224     /* display error if directory is main "options.graphics_directory" etc. */
3225     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3226       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3227
3228     return;
3229   }
3230
3231   while ((dir_entry = readDirectory(dir)) != NULL)      /* loop all entries */
3232   {
3233     char *directory_name = dir_entry->basename;
3234     char *directory_path = getPath2(base_directory, directory_name);
3235
3236     /* skip directory entries for current and parent directory */
3237     if (strEqual(directory_name, ".") ||
3238         strEqual(directory_name, ".."))
3239     {
3240       free(directory_path);
3241
3242       continue;
3243     }
3244
3245     /* skip directory entries which are not a directory */
3246     if (!dir_entry->is_directory)                       /* not a directory */
3247     {
3248       free(directory_path);
3249
3250       continue;
3251     }
3252
3253     free(directory_path);
3254
3255     /* check if this directory contains artwork with or without config file */
3256     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3257                                                         base_directory,
3258                                                         directory_name, type);
3259   }
3260
3261   closeDirectory(dir);
3262
3263   /* check if this directory directly contains artwork itself */
3264   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3265                                                       base_directory, ".",
3266                                                       type);
3267   if (!valid_entry_found)
3268     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3269           base_directory);
3270 }
3271
3272 static TreeInfo *getDummyArtworkInfo(int type)
3273 {
3274   /* this is only needed when there is completely no artwork available */
3275   TreeInfo *artwork_new = newTreeInfo();
3276
3277   setTreeInfoToDefaults(artwork_new, type);
3278
3279   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3280   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3281   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3282
3283   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3284   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3285   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3286
3287   return artwork_new;
3288 }
3289
3290 void LoadArtworkInfo()
3291 {
3292   LoadArtworkInfoCache();
3293
3294   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3295
3296   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3297                                 options.graphics_directory,
3298                                 TREE_TYPE_GRAPHICS_DIR);
3299   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3300                                 getUserGraphicsDir(),
3301                                 TREE_TYPE_GRAPHICS_DIR);
3302
3303   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3304                                 options.sounds_directory,
3305                                 TREE_TYPE_SOUNDS_DIR);
3306   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3307                                 getUserSoundsDir(),
3308                                 TREE_TYPE_SOUNDS_DIR);
3309
3310   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3311                                 options.music_directory,
3312                                 TREE_TYPE_MUSIC_DIR);
3313   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3314                                 getUserMusicDir(),
3315                                 TREE_TYPE_MUSIC_DIR);
3316
3317   if (artwork.gfx_first == NULL)
3318     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3319   if (artwork.snd_first == NULL)
3320     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3321   if (artwork.mus_first == NULL)
3322     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3323
3324   /* before sorting, the first entries will be from the user directory */
3325   artwork.gfx_current =
3326     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3327   if (artwork.gfx_current == NULL)
3328     artwork.gfx_current =
3329       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3330   if (artwork.gfx_current == NULL)
3331     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3332
3333   artwork.snd_current =
3334     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3335   if (artwork.snd_current == NULL)
3336     artwork.snd_current =
3337       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3338   if (artwork.snd_current == NULL)
3339     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3340
3341   artwork.mus_current =
3342     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3343   if (artwork.mus_current == NULL)
3344     artwork.mus_current =
3345       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3346   if (artwork.mus_current == NULL)
3347     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3348
3349   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3350   artwork.snd_current_identifier = artwork.snd_current->identifier;
3351   artwork.mus_current_identifier = artwork.mus_current->identifier;
3352
3353 #if ENABLE_UNUSED_CODE
3354   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3355   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3356   printf("music set == %s\n\n", artwork.mus_current_identifier);
3357 #endif
3358
3359   sortTreeInfo(&artwork.gfx_first);
3360   sortTreeInfo(&artwork.snd_first);
3361   sortTreeInfo(&artwork.mus_first);
3362
3363 #if ENABLE_UNUSED_CODE
3364   dumpTreeInfo(artwork.gfx_first, 0);
3365   dumpTreeInfo(artwork.snd_first, 0);
3366   dumpTreeInfo(artwork.mus_first, 0);
3367 #endif
3368 }
3369
3370 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3371                                   LevelDirTree *level_node)
3372 {
3373   int type = (*artwork_node)->type;
3374
3375   /* recursively check all level directories for artwork sub-directories */
3376
3377   while (level_node)
3378   {
3379     /* check all tree entries for artwork, but skip parent link entries */
3380     if (!level_node->parent_link)
3381     {
3382       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3383       boolean cached = (artwork_new != NULL);
3384
3385       if (cached)
3386       {
3387         pushTreeInfo(artwork_node, artwork_new);
3388       }
3389       else
3390       {
3391         TreeInfo *topnode_last = *artwork_node;
3392         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3393                               ARTWORK_DIRECTORY(type));
3394
3395         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3396
3397         if (topnode_last != *artwork_node)      /* check for newly added node */
3398         {
3399           artwork_new = *artwork_node;
3400
3401           setString(&artwork_new->identifier,   level_node->subdir);
3402           setString(&artwork_new->name,         level_node->name);
3403           setString(&artwork_new->name_sorting, level_node->name_sorting);
3404
3405           artwork_new->sort_priority = level_node->sort_priority;
3406           artwork_new->color = LEVELCOLOR(artwork_new);
3407         }
3408
3409         free(path);
3410       }
3411
3412       /* insert artwork info (from old cache or filesystem) into new cache */
3413       if (artwork_new != NULL)
3414         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3415     }
3416
3417     DrawInitText(level_node->name, 150, FC_YELLOW);
3418
3419     if (level_node->node_group != NULL)
3420       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3421
3422     level_node = level_node->next;
3423   }
3424 }
3425
3426 void LoadLevelArtworkInfo()
3427 {
3428   print_timestamp_init("LoadLevelArtworkInfo");
3429
3430   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3431
3432   print_timestamp_time("DrawTimeText");
3433
3434   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3435   print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3436   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3437   print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3438   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3439   print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3440
3441   SaveArtworkInfoCache();
3442
3443   print_timestamp_time("SaveArtworkInfoCache");
3444
3445   /* needed for reloading level artwork not known at ealier stage */
3446
3447   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3448   {
3449     artwork.gfx_current =
3450       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3451     if (artwork.gfx_current == NULL)
3452       artwork.gfx_current =
3453         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3454     if (artwork.gfx_current == NULL)
3455       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3456   }
3457
3458   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3459   {
3460     artwork.snd_current =
3461       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3462     if (artwork.snd_current == NULL)
3463       artwork.snd_current =
3464         getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3465     if (artwork.snd_current == NULL)
3466       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3467   }
3468
3469   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3470   {
3471     artwork.mus_current =
3472       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3473     if (artwork.mus_current == NULL)
3474       artwork.mus_current =
3475         getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3476     if (artwork.mus_current == NULL)
3477       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3478   }
3479
3480   print_timestamp_time("getTreeInfoFromIdentifier");
3481
3482   sortTreeInfo(&artwork.gfx_first);
3483   sortTreeInfo(&artwork.snd_first);
3484   sortTreeInfo(&artwork.mus_first);
3485
3486   print_timestamp_time("sortTreeInfo");
3487
3488 #if ENABLE_UNUSED_CODE
3489   dumpTreeInfo(artwork.gfx_first, 0);
3490   dumpTreeInfo(artwork.snd_first, 0);
3491   dumpTreeInfo(artwork.mus_first, 0);
3492 #endif
3493
3494   print_timestamp_done("LoadLevelArtworkInfo");
3495 }
3496
3497 static void SaveUserLevelInfo()
3498 {
3499   LevelDirTree *level_info;
3500   char *filename;
3501   FILE *file;
3502   int i;
3503
3504   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3505
3506   if (!(file = fopen(filename, MODE_WRITE)))
3507   {
3508     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3509     free(filename);
3510     return;
3511   }
3512
3513   level_info = newTreeInfo();
3514
3515   /* always start with reliable default values */
3516   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3517
3518   setString(&level_info->name, getLoginName());
3519   setString(&level_info->author, getRealName());
3520   level_info->levels = 100;
3521   level_info->first_level = 1;
3522
3523   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3524
3525   fprintFileHeader(file, LEVELINFO_FILENAME);
3526
3527   ldi = *level_info;
3528   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3529   {
3530     if (i == LEVELINFO_TOKEN_NAME ||
3531         i == LEVELINFO_TOKEN_AUTHOR ||
3532         i == LEVELINFO_TOKEN_LEVELS ||
3533         i == LEVELINFO_TOKEN_FIRST_LEVEL)
3534       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3535
3536     /* just to make things nicer :) */
3537     if (i == LEVELINFO_TOKEN_AUTHOR)
3538       fprintf(file, "\n");      
3539   }
3540
3541   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3542
3543   fclose(file);
3544
3545   SetFilePermissions(filename, PERMS_PRIVATE);
3546
3547   freeTreeInfo(level_info);
3548   free(filename);
3549 }
3550
3551 char *getSetupValue(int type, void *value)
3552 {
3553   static char value_string[MAX_LINE_LEN];
3554
3555   if (value == NULL)
3556     return NULL;
3557
3558   switch (type)
3559   {
3560     case TYPE_BOOLEAN:
3561       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3562       break;
3563
3564     case TYPE_SWITCH:
3565       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3566       break;
3567
3568     case TYPE_SWITCH3:
3569       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3570                             *(int *)value == FALSE ? "off" : "on"));
3571       break;
3572
3573     case TYPE_YES_NO:
3574       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3575       break;
3576
3577     case TYPE_YES_NO_AUTO:
3578       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3579                             *(int *)value == FALSE ? "no" : "yes"));
3580       break;
3581
3582     case TYPE_ECS_AGA:
3583       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3584       break;
3585
3586     case TYPE_KEY:
3587       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3588       break;
3589
3590     case TYPE_KEY_X11:
3591       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3592       break;
3593
3594     case TYPE_INTEGER:
3595       sprintf(value_string, "%d", *(int *)value);
3596       break;
3597
3598     case TYPE_STRING:
3599       if (*(char **)value == NULL)
3600         return NULL;
3601
3602       strcpy(value_string, *(char **)value);
3603       break;
3604
3605     default:
3606       value_string[0] = '\0';
3607       break;
3608   }
3609
3610   if (type & TYPE_GHOSTED)
3611     strcpy(value_string, "n/a");
3612
3613   return value_string;
3614 }
3615
3616 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3617 {
3618   int i;
3619   char *line;
3620   static char token_string[MAX_LINE_LEN];
3621   int token_type = token_info[token_nr].type;
3622   void *setup_value = token_info[token_nr].value;
3623   char *token_text = token_info[token_nr].text;
3624   char *value_string = getSetupValue(token_type, setup_value);
3625
3626   /* build complete token string */
3627   sprintf(token_string, "%s%s", prefix, token_text);
3628
3629   /* build setup entry line */
3630   line = getFormattedSetupEntry(token_string, value_string);
3631
3632   if (token_type == TYPE_KEY_X11)
3633   {
3634     Key key = *(Key *)setup_value;
3635     char *keyname = getKeyNameFromKey(key);
3636
3637     /* add comment, if useful */
3638     if (!strEqual(keyname, "(undefined)") &&
3639         !strEqual(keyname, "(unknown)"))
3640     {
3641       /* add at least one whitespace */
3642       strcat(line, " ");
3643       for (i = strlen(line); i < token_comment_position; i++)
3644         strcat(line, " ");
3645
3646       strcat(line, "# ");
3647       strcat(line, keyname);
3648     }
3649   }
3650
3651   return line;
3652 }
3653
3654 void LoadLevelSetup_LastSeries()
3655 {
3656   /* ----------------------------------------------------------------------- */
3657   /* ~/.<program>/levelsetup.conf                                            */
3658   /* ----------------------------------------------------------------------- */
3659
3660   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3661   SetupFileHash *level_setup_hash = NULL;
3662
3663   /* always start with reliable default values */
3664   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3665
3666   if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3667   {
3668     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3669                                                  DEFAULT_LEVELSET);
3670     if (leveldir_current == NULL)
3671       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3672   }
3673
3674   if ((level_setup_hash = loadSetupFileHash(filename)))
3675   {
3676     char *last_level_series =
3677       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3678
3679     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3680                                                  last_level_series);
3681     if (leveldir_current == NULL)
3682       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3683
3684     freeSetupFileHash(level_setup_hash);
3685   }
3686   else
3687   {
3688     Error(ERR_DEBUG, "using default setup values");
3689   }
3690
3691   free(filename);
3692 }
3693
3694 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3695 {
3696   /* ----------------------------------------------------------------------- */
3697   /* ~/.<program>/levelsetup.conf                                            */
3698   /* ----------------------------------------------------------------------- */
3699
3700   // check if the current level directory structure is available at this point
3701   if (leveldir_current == NULL)
3702     return;
3703
3704   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3705   char *level_subdir = leveldir_current->subdir;
3706   FILE *file;
3707
3708   InitUserDataDirectory();
3709
3710   if (!(file = fopen(filename, MODE_WRITE)))
3711   {
3712     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3713
3714     free(filename);
3715
3716     return;
3717   }
3718
3719   fprintFileHeader(file, LEVELSETUP_FILENAME);
3720
3721   if (deactivate_last_level_series)
3722     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3723
3724   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3725                                                level_subdir));
3726
3727   fclose(file);
3728
3729   SetFilePermissions(filename, PERMS_PRIVATE);
3730
3731   free(filename);
3732 }
3733
3734 void SaveLevelSetup_LastSeries()
3735 {
3736   SaveLevelSetup_LastSeries_Ext(FALSE);
3737 }
3738
3739 void SaveLevelSetup_LastSeries_Deactivate()
3740 {
3741   SaveLevelSetup_LastSeries_Ext(TRUE);
3742 }
3743
3744 static void checkSeriesInfo()
3745 {
3746   static char *level_directory = NULL;
3747   Directory *dir;
3748
3749   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3750
3751   level_directory = getPath2((leveldir_current->in_user_dir ?
3752                               getUserLevelDir(NULL) :
3753                               options.level_directory),
3754                              leveldir_current->fullpath);
3755
3756   if ((dir = openDirectory(level_directory)) == NULL)
3757   {
3758     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3759
3760     return;
3761   }
3762
3763   closeDirectory(dir);
3764 }
3765
3766 void LoadLevelSetup_SeriesInfo()
3767 {
3768   char *filename;
3769   SetupFileHash *level_setup_hash = NULL;
3770   char *level_subdir = leveldir_current->subdir;
3771   int i;
3772
3773   /* always start with reliable default values */
3774   level_nr = leveldir_current->first_level;
3775
3776   for (i = 0; i < MAX_LEVELS; i++)
3777   {
3778     LevelStats_setPlayed(i, 0);
3779     LevelStats_setSolved(i, 0);
3780   }
3781
3782   checkSeriesInfo();
3783
3784   /* ----------------------------------------------------------------------- */
3785   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
3786   /* ----------------------------------------------------------------------- */
3787
3788   level_subdir = leveldir_current->subdir;
3789
3790   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3791
3792   if ((level_setup_hash = loadSetupFileHash(filename)))
3793   {
3794     char *token_value;
3795
3796     /* get last played level in this level set */
3797
3798     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3799
3800     if (token_value)
3801     {
3802       level_nr = atoi(token_value);
3803
3804       if (level_nr < leveldir_current->first_level)
3805         level_nr = leveldir_current->first_level;
3806       if (level_nr > leveldir_current->last_level)
3807         level_nr = leveldir_current->last_level;
3808     }
3809
3810     /* get handicap level in this level set */
3811
3812     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3813
3814     if (token_value)
3815     {
3816       int level_nr = atoi(token_value);
3817
3818       if (level_nr < leveldir_current->first_level)
3819         level_nr = leveldir_current->first_level;
3820       if (level_nr > leveldir_current->last_level + 1)
3821         level_nr = leveldir_current->last_level;
3822
3823       if (leveldir_current->user_defined || !leveldir_current->handicap)
3824         level_nr = leveldir_current->last_level;
3825
3826       leveldir_current->handicap_level = level_nr;
3827     }
3828
3829     /* get number of played and solved levels in this level set */
3830
3831     BEGIN_HASH_ITERATION(level_setup_hash, itr)
3832     {
3833       char *token = HASH_ITERATION_TOKEN(itr);
3834       char *value = HASH_ITERATION_VALUE(itr);
3835
3836       if (strlen(token) == 3 &&
3837           token[0] >= '0' && token[0] <= '9' &&
3838           token[1] >= '0' && token[1] <= '9' &&
3839           token[2] >= '0' && token[2] <= '9')
3840       {
3841         int level_nr = atoi(token);
3842
3843         if (value != NULL)
3844           LevelStats_setPlayed(level_nr, atoi(value));  /* read 1st column */
3845
3846         value = strchr(value, ' ');
3847
3848         if (value != NULL)
3849           LevelStats_setSolved(level_nr, atoi(value));  /* read 2nd column */
3850       }
3851     }
3852     END_HASH_ITERATION(hash, itr)
3853
3854     freeSetupFileHash(level_setup_hash);
3855   }
3856   else
3857   {
3858     Error(ERR_DEBUG, "using default setup values");
3859   }
3860
3861   free(filename);
3862 }
3863
3864 void SaveLevelSetup_SeriesInfo()
3865 {
3866   char *filename;
3867   char *level_subdir = leveldir_current->subdir;
3868   char *level_nr_str = int2str(level_nr, 0);
3869   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3870   FILE *file;
3871   int i;
3872
3873   /* ----------------------------------------------------------------------- */
3874   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
3875   /* ----------------------------------------------------------------------- */
3876
3877   InitLevelSetupDirectory(level_subdir);
3878
3879   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3880
3881   if (!(file = fopen(filename, MODE_WRITE)))
3882   {
3883     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3884     free(filename);
3885     return;
3886   }
3887
3888   fprintFileHeader(file, LEVELSETUP_FILENAME);
3889
3890   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3891                                                level_nr_str));
3892   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3893                                                  handicap_level_str));
3894
3895   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3896        i++)
3897   {
3898     if (LevelStats_getPlayed(i) > 0 ||
3899         LevelStats_getSolved(i) > 0)
3900     {
3901       char token[16];
3902       char value[16];
3903
3904       sprintf(token, "%03d", i);
3905       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3906
3907       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3908     }
3909   }
3910
3911   fclose(file);
3912
3913   SetFilePermissions(filename, PERMS_PRIVATE);
3914
3915   free(filename);
3916 }
3917
3918 int LevelStats_getPlayed(int nr)
3919 {
3920   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3921 }
3922
3923 int LevelStats_getSolved(int nr)
3924 {
3925   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3926 }
3927
3928 void LevelStats_setPlayed(int nr, int value)
3929 {
3930   if (nr >= 0 && nr < MAX_LEVELS)
3931     level_stats[nr].played = value;
3932 }
3933
3934 void LevelStats_setSolved(int nr, int value)
3935 {
3936   if (nr >= 0 && nr < MAX_LEVELS)
3937     level_stats[nr].solved = value;
3938 }
3939
3940 void LevelStats_incPlayed(int nr)
3941 {
3942   if (nr >= 0 && nr < MAX_LEVELS)
3943     level_stats[nr].played++;
3944 }
3945
3946 void LevelStats_incSolved(int nr)
3947 {
3948   if (nr >= 0 && nr < MAX_LEVELS)
3949     level_stats[nr].solved++;
3950 }