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