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