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