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