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