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