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