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