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