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