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