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