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