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