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