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