rnd-20031124-1-src
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <dirent.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "setup.h"
21 #include "joystick.h"
22 #include "text.h"
23 #include "misc.h"
24 #include "hash.h"
25
26
27 #define NUM_LEVELCLASS_DESC     8
28
29 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
30 {
31   "Tutorial Levels",
32   "Classic Originals",
33   "Contributions",
34   "Private Levels",
35   "Boulderdash",
36   "Emerald Mine",
37   "Supaplex",
38   "DX Boulderdash"
39 };
40
41
42 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
43                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
44                          IS_LEVELCLASS_BD(n) ?                  FC_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 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1238 {
1239   if (list == NULL)
1240     return NULL;
1241
1242   if (list->next == NULL)
1243     return (list->next = newSetupFileList(token, value));
1244   else
1245     return addListEntry(list->next, token, value);
1246 }
1247
1248 #ifdef DEBUG
1249 static void printSetupFileList(SetupFileList *list)
1250 {
1251   if (!list)
1252     return;
1253
1254   printf("token: '%s'\n", list->token);
1255   printf("value: '%s'\n", list->value);
1256
1257   printSetupFileList(list->next);
1258 }
1259 #endif
1260
1261 #ifdef DEBUG
1262 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1263 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1264 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1265 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1266 #else
1267 #define insert_hash_entry hashtable_insert
1268 #define search_hash_entry hashtable_search
1269 #define change_hash_entry hashtable_change
1270 #define remove_hash_entry hashtable_remove
1271 #endif
1272
1273 static unsigned int get_hash_from_key(void *key)
1274 {
1275   /*
1276     djb2
1277
1278     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1279     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1280     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1281     it works better than many other constants, prime or not) has never been
1282     adequately explained.
1283
1284     If you just want to have a good hash function, and cannot wait, djb2
1285     is one of the best string hash functions i know. It has excellent
1286     distribution and speed on many different sets of keys and table sizes.
1287     You are not likely to do better with one of the "well known" functions
1288     such as PJW, K&R, etc.
1289
1290     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1291   */
1292
1293   char *str = (char *)key;
1294   unsigned int hash = 5381;
1295   int c;
1296
1297   while ((c = *str++))
1298     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1299
1300   return hash;
1301 }
1302
1303 static int keys_are_equal(void *key1, void *key2)
1304 {
1305   return (strcmp((char *)key1, (char *)key2) == 0);
1306 }
1307
1308 SetupFileHash *newSetupFileHash()
1309 {
1310   SetupFileHash *new_hash =
1311     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1312
1313   if (new_hash == NULL)
1314     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1315
1316   return new_hash;
1317 }
1318
1319 void freeSetupFileHash(SetupFileHash *hash)
1320 {
1321   if (hash == NULL)
1322     return;
1323
1324   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1325 }
1326
1327 char *getHashEntry(SetupFileHash *hash, char *token)
1328 {
1329   if (hash == NULL)
1330     return NULL;
1331
1332   return search_hash_entry(hash, token);
1333 }
1334
1335 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1336 {
1337   char *value_copy;
1338
1339   if (hash == NULL)
1340     return;
1341
1342   value_copy = getStringCopy(value);
1343
1344   /* change value; if it does not exist, insert it as new */
1345   if (!change_hash_entry(hash, token, value_copy))
1346     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1347       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1348 }
1349
1350 #if 0
1351 #ifdef DEBUG
1352 static void printSetupFileHash(SetupFileHash *hash)
1353 {
1354   BEGIN_HASH_ITERATION(hash, itr)
1355   {
1356     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1357     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1358   }
1359   END_HASH_ITERATION(hash, itr)
1360 }
1361 #endif
1362 #endif
1363
1364 static void *loadSetupFileData(char *filename, boolean use_hash)
1365 {
1366   int line_len;
1367   char line[MAX_LINE_LEN];
1368   char *token, *value, *line_ptr;
1369   void *setup_file_data, *insert_ptr = NULL;
1370   FILE *file;
1371
1372   if (use_hash)
1373     setup_file_data = newSetupFileHash();
1374   else
1375     insert_ptr = setup_file_data = newSetupFileList("", "");
1376
1377   if (!(file = fopen(filename, MODE_READ)))
1378   {
1379     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1380     return NULL;
1381   }
1382
1383   while(!feof(file))
1384   {
1385     /* read next line of input file */
1386     if (!fgets(line, MAX_LINE_LEN, file))
1387       break;
1388
1389     /* cut trailing comment or whitespace from input line */
1390     for (line_ptr = line; *line_ptr; line_ptr++)
1391     {
1392       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1393       {
1394         *line_ptr = '\0';
1395         break;
1396       }
1397     }
1398
1399     /* cut trailing whitespaces from input line */
1400     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1401       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1402         *line_ptr = '\0';
1403
1404     /* ignore empty lines */
1405     if (*line == '\0')
1406       continue;
1407
1408     line_len = strlen(line);
1409
1410     /* cut leading whitespaces from token */
1411     for (token = line; *token; token++)
1412       if (*token != ' ' && *token != '\t')
1413         break;
1414
1415     /* find end of token */
1416     for (line_ptr = token; *line_ptr; line_ptr++)
1417     {
1418       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1419       {
1420         *line_ptr = '\0';
1421         break;
1422       }
1423     }
1424
1425     if (line_ptr < line + line_len)
1426       value = line_ptr + 1;
1427     else
1428 #if 1
1429       value = "true";   /* treat tokens without value as "true" */
1430 #else
1431       value = "\0";
1432 #endif
1433
1434     /* cut leading whitespaces from value */
1435     for (; *value; value++)
1436       if (*value != ' ' && *value != '\t')
1437         break;
1438
1439     if (*token && *value)
1440     {
1441       if (use_hash)
1442         setHashEntry((SetupFileHash *)setup_file_data, token, value);
1443       else
1444         insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1445     }
1446   }
1447
1448   fclose(file);
1449
1450   if (use_hash)
1451   {
1452     if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1453       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1454   }
1455   else
1456   {
1457     SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1458     SetupFileList *first_valid_list_entry = setup_file_list->next;
1459
1460     /* free empty list header */
1461     setup_file_list->next = NULL;
1462     freeSetupFileList(setup_file_list);
1463     setup_file_data = first_valid_list_entry;
1464
1465     if (first_valid_list_entry == NULL)
1466       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1467   }
1468
1469   return setup_file_data;
1470 }
1471
1472 SetupFileList *loadSetupFileList(char *filename)
1473 {
1474   return (SetupFileList *)loadSetupFileData(filename, FALSE);
1475 }
1476
1477 SetupFileHash *loadSetupFileHash(char *filename)
1478 {
1479   return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1480 }
1481
1482 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1483                                   char *identifier)
1484 {
1485   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1486
1487   if (value == NULL)
1488     Error(ERR_WARN, "configuration file has no file identifier");
1489   else if (!checkCookieString(value, identifier))
1490     Error(ERR_WARN, "configuration file has wrong file identifier");
1491 }
1492
1493
1494 /* ========================================================================= */
1495 /* setup file stuff                                                          */
1496 /* ========================================================================= */
1497
1498 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1499 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1500 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1501
1502 /* level directory info */
1503 #define LEVELINFO_TOKEN_IDENTIFIER      0
1504 #define LEVELINFO_TOKEN_NAME            1
1505 #define LEVELINFO_TOKEN_NAME_SORTING    2
1506 #define LEVELINFO_TOKEN_AUTHOR          3
1507 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1508 #define LEVELINFO_TOKEN_LEVELS          5
1509 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
1510 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
1511 #define LEVELINFO_TOKEN_LATEST_ENGINE   8
1512 #define LEVELINFO_TOKEN_LEVEL_GROUP     9
1513 #define LEVELINFO_TOKEN_READONLY        10
1514 #define LEVELINFO_TOKEN_GRAPHICS_SET    11
1515 #define LEVELINFO_TOKEN_SOUNDS_SET      12
1516 #define LEVELINFO_TOKEN_MUSIC_SET       13
1517
1518 #define NUM_LEVELINFO_TOKENS            14
1519
1520 static LevelDirTree ldi;
1521
1522 static struct TokenInfo levelinfo_tokens[] =
1523 {
1524   /* level directory info */
1525   { TYPE_STRING,  &ldi.identifier,      "identifier"    },
1526   { TYPE_STRING,  &ldi.name,            "name"          },
1527   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1528   { TYPE_STRING,  &ldi.author,          "author"        },
1529   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1530   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1531   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1532   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1533   { TYPE_BOOLEAN, &ldi.latest_engine,   "latest_engine" },
1534   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1535   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      },
1536   { TYPE_STRING,  &ldi.graphics_set,    "graphics_set"  },
1537   { TYPE_STRING,  &ldi.sounds_set,      "sounds_set"    },
1538   { TYPE_STRING,  &ldi.music_set,       "music_set"     }
1539 };
1540
1541 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1542 {
1543   ldi->type = type;
1544
1545   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1546                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1547                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1548                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1549                    NULL);
1550
1551   ldi->node_parent = NULL;
1552   ldi->node_group = NULL;
1553   ldi->next = NULL;
1554
1555   ldi->cl_first = -1;
1556   ldi->cl_cursor = -1;
1557
1558   ldi->filename = NULL;
1559   ldi->fullpath = NULL;
1560   ldi->basepath = NULL;
1561   ldi->identifier = NULL;
1562   ldi->name = getStringCopy(ANONYMOUS_NAME);
1563   ldi->name_sorting = NULL;
1564   ldi->author = getStringCopy(ANONYMOUS_NAME);
1565
1566   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1567   ldi->latest_engine = FALSE;                   /* default: get from level */
1568   ldi->parent_link = FALSE;
1569   ldi->user_defined = FALSE;
1570   ldi->color = 0;
1571   ldi->class_desc = NULL;
1572
1573   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1574   {
1575     ldi->imported_from = NULL;
1576
1577     ldi->graphics_set = NULL;
1578     ldi->sounds_set = NULL;
1579     ldi->music_set = NULL;
1580     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1581     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1582     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1583
1584     ldi->levels = 0;
1585     ldi->first_level = 0;
1586     ldi->last_level = 0;
1587     ldi->level_group = FALSE;
1588     ldi->handicap_level = 0;
1589     ldi->readonly = TRUE;
1590   }
1591 }
1592
1593 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1594 {
1595   if (parent == NULL)
1596   {
1597     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1598
1599     setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
1600
1601     return;
1602   }
1603
1604 #if 1
1605   /* copy all values from the parent structure */
1606
1607   ldi->type = parent->type;
1608
1609   ldi->node_top = parent->node_top;
1610   ldi->node_parent = parent;
1611   ldi->node_group = NULL;
1612   ldi->next = NULL;
1613
1614   ldi->cl_first = -1;
1615   ldi->cl_cursor = -1;
1616
1617   ldi->filename = NULL;
1618   ldi->fullpath = NULL;
1619   ldi->basepath = NULL;
1620   ldi->identifier = NULL;
1621   ldi->name = getStringCopy(ANONYMOUS_NAME);
1622   ldi->name_sorting = NULL;
1623   ldi->author = getStringCopy(parent->author);
1624
1625   ldi->sort_priority = parent->sort_priority;
1626   ldi->latest_engine = parent->latest_engine;
1627   ldi->parent_link = FALSE;
1628   ldi->user_defined = parent->user_defined;
1629   ldi->color = parent->color;
1630   ldi->class_desc = getStringCopy(parent->class_desc);
1631
1632   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1633   {
1634     ldi->imported_from = getStringCopy(parent->imported_from);
1635
1636     ldi->graphics_set = NULL;
1637     ldi->sounds_set = NULL;
1638     ldi->music_set = NULL;
1639     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1640     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1641     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1642
1643     ldi->levels = 0;
1644     ldi->first_level = 0;
1645     ldi->last_level = 0;
1646     ldi->level_group = FALSE;
1647     ldi->handicap_level = 0;
1648     ldi->readonly = TRUE;
1649   }
1650
1651
1652 #else
1653
1654   /* first copy all values from the parent structure ... */
1655   *ldi = *parent;
1656
1657   /* ... then set all fields to default that cannot be inherited from parent.
1658      This is especially important for all those fields that can be set from
1659      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1660      calls 'free()' for all already set token values which requires that no
1661      other structure's pointer may point to them!
1662   */
1663
1664   ldi->filename = NULL;
1665   ldi->fullpath = NULL;
1666   ldi->basepath = NULL;
1667   ldi->identifier = NULL;
1668   ldi->name = getStringCopy(ANONYMOUS_NAME);
1669   ldi->name_sorting = NULL;
1670   ldi->author = getStringCopy(parent->author);
1671
1672   ldi->imported_from = getStringCopy(parent->imported_from);
1673   ldi->class_desc = getStringCopy(parent->class_desc);
1674
1675   ldi->graphics_set = NULL;
1676   ldi->sounds_set = NULL;
1677   ldi->music_set = NULL;
1678   ldi->graphics_path = NULL;
1679   ldi->sounds_path = NULL;
1680   ldi->music_path = NULL;
1681
1682   ldi->level_group = FALSE;
1683   ldi->parent_link = FALSE;
1684
1685   ldi->node_top = parent->node_top;
1686   ldi->node_parent = parent;
1687   ldi->node_group = NULL;
1688   ldi->next = NULL;
1689
1690 #endif
1691 }
1692
1693 static void freeTreeInfo(TreeInfo *ldi)
1694 {
1695   if (ldi->filename)
1696     free(ldi->filename);
1697   if (ldi->fullpath)
1698     free(ldi->fullpath);
1699   if (ldi->basepath)
1700     free(ldi->basepath);
1701   if (ldi->identifier)
1702     free(ldi->identifier);
1703
1704   if (ldi->name)
1705     free(ldi->name);
1706   if (ldi->name_sorting)
1707     free(ldi->name_sorting);
1708   if (ldi->author)
1709     free(ldi->author);
1710
1711   if (ldi->class_desc)
1712     free(ldi->class_desc);
1713
1714   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1715   {
1716     if (ldi->graphics_set)
1717       free(ldi->graphics_set);
1718     if (ldi->sounds_set)
1719       free(ldi->sounds_set);
1720     if (ldi->music_set)
1721       free(ldi->music_set);
1722
1723     if (ldi->graphics_path)
1724       free(ldi->graphics_path);
1725     if (ldi->sounds_path)
1726       free(ldi->sounds_path);
1727     if (ldi->music_path)
1728       free(ldi->music_path);
1729   }
1730 }
1731
1732 void setSetupInfo(struct TokenInfo *token_info,
1733                   int token_nr, char *token_value)
1734 {
1735   int token_type = token_info[token_nr].type;
1736   void *setup_value = token_info[token_nr].value;
1737
1738   if (token_value == NULL)
1739     return;
1740
1741   /* set setup field to corresponding token value */
1742   switch (token_type)
1743   {
1744     case TYPE_BOOLEAN:
1745     case TYPE_SWITCH:
1746       *(boolean *)setup_value = get_boolean_from_string(token_value);
1747       break;
1748
1749     case TYPE_KEY:
1750       *(Key *)setup_value = getKeyFromKeyName(token_value);
1751       break;
1752
1753     case TYPE_KEY_X11:
1754       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1755       break;
1756
1757     case TYPE_INTEGER:
1758       *(int *)setup_value = get_integer_from_string(token_value);
1759       break;
1760
1761     case TYPE_STRING:
1762       if (*(char **)setup_value != NULL)
1763         free(*(char **)setup_value);
1764       *(char **)setup_value = getStringCopy(token_value);
1765       break;
1766
1767     default:
1768       break;
1769   }
1770 }
1771
1772 static int compareTreeInfoEntries(const void *object1, const void *object2)
1773 {
1774   const TreeInfo *entry1 = *((TreeInfo **)object1);
1775   const TreeInfo *entry2 = *((TreeInfo **)object2);
1776   int class_sorting1, class_sorting2;
1777   int compare_result;
1778
1779   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1780   {
1781     class_sorting1 = LEVELSORTING(entry1);
1782     class_sorting2 = LEVELSORTING(entry2);
1783   }
1784   else
1785   {
1786     class_sorting1 = ARTWORKSORTING(entry1);
1787     class_sorting2 = ARTWORKSORTING(entry2);
1788   }
1789
1790   if (entry1->parent_link || entry2->parent_link)
1791     compare_result = (entry1->parent_link ? -1 : +1);
1792   else if (entry1->sort_priority == entry2->sort_priority)
1793   {
1794     char *name1 = getStringToLower(entry1->name_sorting);
1795     char *name2 = getStringToLower(entry2->name_sorting);
1796
1797     compare_result = strcmp(name1, name2);
1798
1799     free(name1);
1800     free(name2);
1801   }
1802   else if (class_sorting1 == class_sorting2)
1803     compare_result = entry1->sort_priority - entry2->sort_priority;
1804   else
1805     compare_result = class_sorting1 - class_sorting2;
1806
1807   return compare_result;
1808 }
1809
1810 static void createParentTreeInfoNode(TreeInfo *node_parent)
1811 {
1812   TreeInfo *ti_new;
1813
1814   if (node_parent == NULL)
1815     return;
1816
1817   ti_new = newTreeInfo();
1818   setTreeInfoToDefaults(ti_new, node_parent->type);
1819
1820   ti_new->node_parent = node_parent;
1821   ti_new->parent_link = TRUE;
1822
1823 #if 1
1824   setString(&ti_new->identifier, node_parent->identifier);
1825   setString(&ti_new->name, ".. (parent directory)");
1826   setString(&ti_new->name_sorting, ti_new->name);
1827
1828   setString(&ti_new->filename, "..");
1829   setString(&ti_new->fullpath, node_parent->fullpath);
1830
1831   ti_new->sort_priority = node_parent->sort_priority;
1832   ti_new->latest_engine = node_parent->latest_engine;
1833
1834   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
1835 #else
1836   ti_new->identifier = getStringCopy(node_parent->identifier);
1837   ti_new->name = ".. (parent directory)";
1838   ti_new->name_sorting = getStringCopy(ti_new->name);
1839
1840   ti_new->filename = "..";
1841   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1842
1843   ti_new->sort_priority = node_parent->sort_priority;
1844   ti_new->latest_engine = node_parent->latest_engine;
1845
1846   ti_new->class_desc = getLevelClassDescription(ti_new);
1847 #endif
1848
1849   pushTreeInfo(&node_parent->node_group, ti_new);
1850 }
1851
1852 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1853 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1854
1855 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1856                                           TreeInfo *node_parent,
1857                                           char *level_directory,
1858                                           char *directory_name)
1859 {
1860   char *directory_path = getPath2(level_directory, directory_name);
1861   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1862   SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1863   LevelDirTree *leveldir_new = NULL;
1864   int i;
1865
1866   if (setup_file_hash == NULL)
1867   {
1868     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1869
1870     free(directory_path);
1871     free(filename);
1872
1873     return FALSE;
1874   }
1875
1876   leveldir_new = newTreeInfo();
1877
1878   if (node_parent)
1879     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1880   else
1881     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1882
1883   leveldir_new->filename = getStringCopy(directory_name);
1884
1885   checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1886
1887   /* set all structure fields according to the token/value pairs */
1888   ldi = *leveldir_new;
1889   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1890     setSetupInfo(levelinfo_tokens, i,
1891                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1892   *leveldir_new = ldi;
1893
1894 #if 1
1895   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1896     setString(&leveldir_new->name, leveldir_new->filename);
1897 #else
1898   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1899   {
1900     free(leveldir_new->name);
1901     leveldir_new->name = getStringCopy(leveldir_new->filename);
1902   }
1903 #endif
1904
1905   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1906
1907   if (leveldir_new->identifier == NULL)
1908     leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1909
1910   if (leveldir_new->name_sorting == NULL)
1911     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1912
1913   if (node_parent == NULL)              /* top level group */
1914   {
1915     leveldir_new->basepath = getStringCopy(level_directory);
1916     leveldir_new->fullpath = getStringCopy(leveldir_new->filename);
1917   }
1918   else                                  /* sub level group */
1919   {
1920     leveldir_new->basepath = getStringCopy(node_parent->basepath);
1921     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1922   }
1923
1924   if (leveldir_new->levels < 1)
1925     leveldir_new->levels = 1;
1926
1927   leveldir_new->last_level =
1928     leveldir_new->first_level + leveldir_new->levels - 1;
1929
1930 #if 1
1931   leveldir_new->user_defined =
1932     (strcmp(leveldir_new->basepath, options.level_directory) != 0);
1933 #else
1934   leveldir_new->user_defined =
1935     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1936 #endif
1937
1938   leveldir_new->color = LEVELCOLOR(leveldir_new);
1939 #if 1
1940   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
1941 #else
1942   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1943 #endif
1944
1945   leveldir_new->handicap_level =        /* set handicap to default value */
1946     (leveldir_new->user_defined ?
1947      leveldir_new->last_level :
1948      leveldir_new->first_level);
1949
1950   pushTreeInfo(node_first, leveldir_new);
1951
1952   freeSetupFileHash(setup_file_hash);
1953
1954   if (leveldir_new->level_group)
1955   {
1956     /* create node to link back to current level directory */
1957     createParentTreeInfoNode(leveldir_new);
1958
1959     /* step into sub-directory and look for more level series */
1960     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1961                               leveldir_new, directory_path);
1962   }
1963
1964   free(directory_path);
1965   free(filename);
1966
1967   return TRUE;
1968 }
1969
1970 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1971                                       TreeInfo *node_parent,
1972                                       char *level_directory)
1973 {
1974   DIR *dir;
1975   struct dirent *dir_entry;
1976   boolean valid_entry_found = FALSE;
1977
1978   if ((dir = opendir(level_directory)) == NULL)
1979   {
1980     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1981     return;
1982   }
1983
1984   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1985   {
1986     struct stat file_status;
1987     char *directory_name = dir_entry->d_name;
1988     char *directory_path = getPath2(level_directory, directory_name);
1989
1990     /* skip entries for current and parent directory */
1991     if (strcmp(directory_name, ".")  == 0 ||
1992         strcmp(directory_name, "..") == 0)
1993     {
1994       free(directory_path);
1995       continue;
1996     }
1997
1998     /* find out if directory entry is itself a directory */
1999     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2000         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2001     {
2002       free(directory_path);
2003       continue;
2004     }
2005
2006     free(directory_path);
2007
2008     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
2009         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
2010         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
2011       continue;
2012
2013     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2014                                                     level_directory,
2015                                                     directory_name);
2016   }
2017
2018   closedir(dir);
2019
2020   if (!valid_entry_found)
2021   {
2022     /* check if this directory directly contains a file "levelinfo.conf" */
2023     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2024                                                     level_directory, ".");
2025   }
2026
2027   if (!valid_entry_found)
2028     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2029           level_directory);
2030 }
2031
2032 void LoadLevelInfo()
2033 {
2034   InitUserLevelDirectory(getLoginName());
2035
2036   DrawInitText("Loading level series:", 120, FC_GREEN);
2037
2038   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2039   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2040
2041   /* before sorting, the first entries will be from the user directory */
2042   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2043
2044   if (leveldir_first == NULL)
2045     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2046
2047   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
2048
2049 #if 0
2050   dumpTreeInfo(leveldir_first, 0);
2051 #endif
2052 }
2053
2054 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2055                                               TreeInfo *node_parent,
2056                                               char *base_directory,
2057                                               char *directory_name, int type)
2058 {
2059   char *directory_path = getPath2(base_directory, directory_name);
2060   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2061   SetupFileHash *setup_file_hash = NULL;
2062   TreeInfo *artwork_new = NULL;
2063   int i;
2064
2065   if (access(filename, F_OK) == 0)              /* file exists */
2066     setup_file_hash = loadSetupFileHash(filename);
2067
2068   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2069   {
2070     DIR *dir;
2071     struct dirent *dir_entry;
2072     boolean valid_file_found = FALSE;
2073
2074     if ((dir = opendir(directory_path)) != NULL)
2075     {
2076       while ((dir_entry = readdir(dir)) != NULL)
2077       {
2078         char *entry_name = dir_entry->d_name;
2079
2080         if (FileIsArtworkType(entry_name, type))
2081         {
2082           valid_file_found = TRUE;
2083           break;
2084         }
2085       }
2086
2087       closedir(dir);
2088     }
2089
2090     if (!valid_file_found)
2091     {
2092       if (strcmp(directory_name, ".") != 0)
2093         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2094
2095       free(directory_path);
2096       free(filename);
2097
2098       return FALSE;
2099     }
2100   }
2101
2102   artwork_new = newTreeInfo();
2103
2104   if (node_parent)
2105     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2106   else
2107     setTreeInfoToDefaults(artwork_new, type);
2108
2109   artwork_new->filename = getStringCopy(directory_name);
2110
2111   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2112   {
2113 #if 0
2114     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
2115 #endif
2116
2117     /* set all structure fields according to the token/value pairs */
2118     ldi = *artwork_new;
2119     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2120       setSetupInfo(levelinfo_tokens, i,
2121                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2122     *artwork_new = ldi;
2123
2124 #if 1
2125     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2126       setString(&artwork_new->name, artwork_new->filename);
2127 #else
2128     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2129     {
2130       free(artwork_new->name);
2131       artwork_new->name = getStringCopy(artwork_new->filename);
2132     }
2133 #endif
2134
2135 #if 0
2136     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2137 #endif
2138
2139     if (artwork_new->identifier == NULL)
2140       artwork_new->identifier = getStringCopy(artwork_new->filename);
2141
2142     if (artwork_new->name_sorting == NULL)
2143       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2144   }
2145
2146   if (node_parent == NULL)              /* top level group */
2147   {
2148     artwork_new->basepath = getStringCopy(base_directory);
2149     artwork_new->fullpath = getStringCopy(artwork_new->filename);
2150   }
2151   else                                  /* sub level group */
2152   {
2153     artwork_new->basepath = getStringCopy(node_parent->basepath);
2154     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2155   }
2156
2157 #if 1
2158   artwork_new->user_defined =
2159     (strcmp(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)) != 0);
2160 #else
2161   artwork_new->user_defined =
2162     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
2163 #endif
2164
2165   /* (may use ".sort_priority" from "setup_file_hash" above) */
2166   artwork_new->color = ARTWORKCOLOR(artwork_new);
2167 #if 1
2168   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2169 #else
2170   artwork_new->class_desc = getLevelClassDescription(artwork_new);
2171 #endif
2172
2173   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2174   {
2175 #if 0
2176     if (artwork_new->name != NULL)
2177     {
2178       free(artwork_new->name);
2179       artwork_new->name = NULL;
2180     }
2181 #endif
2182
2183 #if 0
2184     if (artwork_new->identifier != NULL)
2185     {
2186       free(artwork_new->identifier);
2187       artwork_new->identifier = NULL;
2188     }
2189 #endif
2190
2191     if (strcmp(artwork_new->filename, ".") == 0)
2192     {
2193       if (artwork_new->user_defined)
2194       {
2195 #if 1
2196         setString(&artwork_new->identifier, "private");
2197 #else
2198         artwork_new->identifier = getStringCopy("private");
2199 #endif
2200         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2201       }
2202       else
2203       {
2204 #if 1
2205         setString(&artwork_new->identifier, "classic");
2206 #else
2207         artwork_new->identifier = getStringCopy("classic");
2208 #endif
2209         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2210       }
2211
2212       /* set to new values after changing ".sort_priority" */
2213       artwork_new->color = ARTWORKCOLOR(artwork_new);
2214 #if 1
2215       setString(&artwork_new->class_desc,
2216                 getLevelClassDescription(artwork_new));
2217 #else
2218       artwork_new->class_desc = getLevelClassDescription(artwork_new);
2219 #endif
2220     }
2221     else
2222     {
2223 #if 1
2224       setString(&artwork_new->identifier, artwork_new->filename);
2225 #else
2226       artwork_new->identifier = getStringCopy(artwork_new->filename);
2227 #endif
2228     }
2229
2230 #if 1
2231     setString(&artwork_new->name, artwork_new->identifier);
2232     setString(&artwork_new->name_sorting, artwork_new->name);
2233 #else
2234     artwork_new->name = getStringCopy(artwork_new->identifier);
2235     artwork_new->name_sorting = getStringCopy(artwork_new->name);
2236 #endif
2237   }
2238
2239   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2240
2241   pushTreeInfo(node_first, artwork_new);
2242
2243   freeSetupFileHash(setup_file_hash);
2244
2245   free(directory_path);
2246   free(filename);
2247
2248   return TRUE;
2249 }
2250
2251 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2252                                           TreeInfo *node_parent,
2253                                           char *base_directory, int type)
2254 {
2255   DIR *dir;
2256   struct dirent *dir_entry;
2257   boolean valid_entry_found = FALSE;
2258
2259   if ((dir = opendir(base_directory)) == NULL)
2260   {
2261     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2262       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2263     return;
2264   }
2265
2266   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2267   {
2268     struct stat file_status;
2269     char *directory_name = dir_entry->d_name;
2270     char *directory_path = getPath2(base_directory, directory_name);
2271
2272     /* skip entries for current and parent directory */
2273     if (strcmp(directory_name, ".")  == 0 ||
2274         strcmp(directory_name, "..") == 0)
2275     {
2276       free(directory_path);
2277       continue;
2278     }
2279
2280     /* find out if directory entry is itself a directory */
2281     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2282         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2283     {
2284       free(directory_path);
2285       continue;
2286     }
2287
2288     free(directory_path);
2289
2290     /* check if this directory contains artwork with or without config file */
2291     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2292                                                         base_directory,
2293                                                         directory_name, type);
2294   }
2295
2296   closedir(dir);
2297
2298   /* check if this directory directly contains artwork itself */
2299   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2300                                                       base_directory, ".",
2301                                                       type);
2302   if (!valid_entry_found)
2303     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2304           base_directory);
2305 }
2306
2307 static TreeInfo *getDummyArtworkInfo(int type)
2308 {
2309   /* this is only needed when there is completely no artwork available */
2310   TreeInfo *artwork_new = newTreeInfo();
2311
2312   setTreeInfoToDefaults(artwork_new, type);
2313
2314 #if 1
2315   setString(&artwork_new->filename, UNDEFINED_FILENAME);
2316   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2317   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2318
2319   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2320   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2321   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2322 #else
2323   artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
2324   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2325   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2326
2327   if (artwork_new->name != NULL)
2328     free(artwork_new->name);
2329
2330   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
2331   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
2332   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2333 #endif
2334
2335   return artwork_new;
2336 }
2337
2338 void LoadArtworkInfo()
2339 {
2340   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2341
2342   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2343                                 options.graphics_directory,
2344                                 TREE_TYPE_GRAPHICS_DIR);
2345   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2346                                 getUserGraphicsDir(),
2347                                 TREE_TYPE_GRAPHICS_DIR);
2348
2349   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2350                                 options.sounds_directory,
2351                                 TREE_TYPE_SOUNDS_DIR);
2352   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2353                                 getUserSoundsDir(),
2354                                 TREE_TYPE_SOUNDS_DIR);
2355
2356   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2357                                 options.music_directory,
2358                                 TREE_TYPE_MUSIC_DIR);
2359   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2360                                 getUserMusicDir(),
2361                                 TREE_TYPE_MUSIC_DIR);
2362
2363   if (artwork.gfx_first == NULL)
2364     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2365   if (artwork.snd_first == NULL)
2366     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2367   if (artwork.mus_first == NULL)
2368     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2369
2370   /* before sorting, the first entries will be from the user directory */
2371   artwork.gfx_current =
2372     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2373   if (artwork.gfx_current == NULL)
2374     artwork.gfx_current =
2375       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2376   if (artwork.gfx_current == NULL)
2377     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2378
2379   artwork.snd_current =
2380     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2381   if (artwork.snd_current == NULL)
2382     artwork.snd_current =
2383       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2384   if (artwork.snd_current == NULL)
2385     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2386
2387   artwork.mus_current =
2388     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2389   if (artwork.mus_current == NULL)
2390     artwork.mus_current =
2391       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2392   if (artwork.mus_current == NULL)
2393     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2394
2395   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2396   artwork.snd_current_identifier = artwork.snd_current->identifier;
2397   artwork.mus_current_identifier = artwork.mus_current->identifier;
2398
2399 #if 0
2400   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2401   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2402   printf("music set == %s\n\n", artwork.mus_current_identifier);
2403 #endif
2404
2405   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2406   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2407   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2408
2409 #if 0
2410   dumpTreeInfo(artwork.gfx_first, 0);
2411   dumpTreeInfo(artwork.snd_first, 0);
2412   dumpTreeInfo(artwork.mus_first, 0);
2413 #endif
2414 }
2415
2416 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2417                                   LevelDirTree *level_node)
2418 {
2419   /* recursively check all level directories for artwork sub-directories */
2420
2421   while (level_node)
2422   {
2423     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2424                           ARTWORK_DIRECTORY((*artwork_node)->type));
2425
2426 #if 0
2427     if (!level_node->parent_link)
2428       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2429              level_node->filename, level_node->name);
2430 #endif
2431
2432     if (!level_node->parent_link)
2433     {
2434       TreeInfo *topnode_last = *artwork_node;
2435
2436       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2437                                     (*artwork_node)->type);
2438
2439       if (topnode_last != *artwork_node)
2440       {
2441         free((*artwork_node)->identifier);
2442         free((*artwork_node)->name);
2443         free((*artwork_node)->name_sorting);
2444
2445         (*artwork_node)->identifier   = getStringCopy(level_node->filename);
2446         (*artwork_node)->name         = getStringCopy(level_node->name);
2447         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2448
2449         (*artwork_node)->sort_priority = level_node->sort_priority;
2450         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2451       }
2452     }
2453
2454     free(path);
2455
2456     if (level_node->node_group != NULL)
2457       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2458
2459     level_node = level_node->next;
2460   }
2461 }
2462
2463 void LoadLevelArtworkInfo()
2464 {
2465   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2466
2467   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2468   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2469   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2470
2471   /* needed for reloading level artwork not known at ealier stage */
2472
2473   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2474   {
2475     artwork.gfx_current =
2476       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2477     if (artwork.gfx_current == NULL)
2478       artwork.gfx_current =
2479         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2480     if (artwork.gfx_current == NULL)
2481       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2482   }
2483
2484   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2485   {
2486     artwork.snd_current =
2487       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2488     if (artwork.snd_current == NULL)
2489       artwork.snd_current =
2490         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2491     if (artwork.snd_current == NULL)
2492       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2493   }
2494
2495   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2496   {
2497     artwork.mus_current =
2498       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2499     if (artwork.mus_current == NULL)
2500       artwork.mus_current =
2501         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2502     if (artwork.mus_current == NULL)
2503       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2504   }
2505
2506   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2507   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2508   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2509
2510 #if 0
2511   dumpTreeInfo(artwork.gfx_first, 0);
2512   dumpTreeInfo(artwork.snd_first, 0);
2513   dumpTreeInfo(artwork.mus_first, 0);
2514 #endif
2515 }
2516
2517 static void SaveUserLevelInfo()
2518 {
2519   LevelDirTree *level_info;
2520   char *filename;
2521   FILE *file;
2522   int i;
2523
2524   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2525
2526   if (!(file = fopen(filename, MODE_WRITE)))
2527   {
2528     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2529     free(filename);
2530     return;
2531   }
2532
2533   level_info = newTreeInfo();
2534
2535   /* always start with reliable default values */
2536   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2537
2538 #if 1
2539   setString(&level_info->name, getLoginName());
2540   setString(&level_info->author, getRealName());
2541   level_info->levels = 100;
2542   level_info->first_level = 1;
2543   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
2544   level_info->readonly = FALSE;
2545   setString(&level_info->graphics_set, GFX_CLASSIC_SUBDIR);
2546   setString(&level_info->sounds_set,   SND_CLASSIC_SUBDIR);
2547   setString(&level_info->music_set,    MUS_CLASSIC_SUBDIR);
2548 #else
2549   ldi.name = getStringCopy(getLoginName());
2550   ldi.author = getStringCopy(getRealName());
2551   ldi.levels = 100;
2552   ldi.first_level = 1;
2553   ldi.sort_priority = LEVELCLASS_PRIVATE_START;
2554   ldi.readonly = FALSE;
2555   ldi.graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
2556   ldi.sounds_set = getStringCopy(SND_CLASSIC_SUBDIR);
2557   ldi.music_set = getStringCopy(MUS_CLASSIC_SUBDIR);
2558 #endif
2559
2560   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2561                                                  getCookie("LEVELINFO")));
2562
2563   ldi = *level_info;
2564   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2565     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2566         i != LEVELINFO_TOKEN_NAME_SORTING &&
2567         i != LEVELINFO_TOKEN_IMPORTED_FROM)
2568       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2569
2570   fclose(file);
2571
2572   SetFilePermissions(filename, PERMS_PRIVATE);
2573
2574   freeTreeInfo(level_info);
2575   free(filename);
2576 }
2577
2578 char *getSetupValue(int type, void *value)
2579 {
2580   static char value_string[MAX_LINE_LEN];
2581
2582   if (value == NULL)
2583     return NULL;
2584
2585   switch (type)
2586   {
2587     case TYPE_BOOLEAN:
2588       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2589       break;
2590
2591     case TYPE_SWITCH:
2592       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2593       break;
2594
2595     case TYPE_YES_NO:
2596       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2597       break;
2598
2599     case TYPE_KEY:
2600       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2601       break;
2602
2603     case TYPE_KEY_X11:
2604       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2605       break;
2606
2607     case TYPE_INTEGER:
2608       sprintf(value_string, "%d", *(int *)value);
2609       break;
2610
2611     case TYPE_STRING:
2612       strcpy(value_string, *(char **)value);
2613       break;
2614
2615     default:
2616       value_string[0] = '\0';
2617       break;
2618   }
2619
2620   return value_string;
2621 }
2622
2623 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2624 {
2625   int i;
2626   char *line;
2627   static char token_string[MAX_LINE_LEN];
2628   int token_type = token_info[token_nr].type;
2629   void *setup_value = token_info[token_nr].value;
2630   char *token_text = token_info[token_nr].text;
2631   char *value_string = getSetupValue(token_type, setup_value);
2632
2633   /* build complete token string */
2634   sprintf(token_string, "%s%s", prefix, token_text);
2635
2636   /* build setup entry line */
2637   line = getFormattedSetupEntry(token_string, value_string);
2638
2639   if (token_type == TYPE_KEY_X11)
2640   {
2641     Key key = *(Key *)setup_value;
2642     char *keyname = getKeyNameFromKey(key);
2643
2644     /* add comment, if useful */
2645     if (strcmp(keyname, "(undefined)") != 0 &&
2646         strcmp(keyname, "(unknown)") != 0)
2647     {
2648       /* add at least one whitespace */
2649       strcat(line, " ");
2650       for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2651         strcat(line, " ");
2652
2653       strcat(line, "# ");
2654       strcat(line, keyname);
2655     }
2656   }
2657
2658   return line;
2659 }
2660
2661 void LoadLevelSetup_LastSeries()
2662 {
2663   /* ----------------------------------------------------------------------- */
2664   /* ~/.<program>/levelsetup.conf                                            */
2665   /* ----------------------------------------------------------------------- */
2666
2667   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2668   SetupFileHash *level_setup_hash = NULL;
2669
2670   /* always start with reliable default values */
2671   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2672
2673   if ((level_setup_hash = loadSetupFileHash(filename)))
2674   {
2675     char *last_level_series =
2676       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2677
2678     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2679                                                  last_level_series);
2680     if (leveldir_current == NULL)
2681       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2682
2683     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2684
2685     freeSetupFileHash(level_setup_hash);
2686   }
2687   else
2688     Error(ERR_WARN, "using default setup values");
2689
2690   free(filename);
2691 }
2692
2693 void SaveLevelSetup_LastSeries()
2694 {
2695   /* ----------------------------------------------------------------------- */
2696   /* ~/.<program>/levelsetup.conf                                            */
2697   /* ----------------------------------------------------------------------- */
2698
2699   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2700   char *level_subdir = leveldir_current->filename;
2701   FILE *file;
2702
2703   InitUserDataDirectory();
2704
2705   if (!(file = fopen(filename, MODE_WRITE)))
2706   {
2707     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2708     free(filename);
2709     return;
2710   }
2711
2712   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2713                                                  getCookie("LEVELSETUP")));
2714   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2715                                                level_subdir));
2716
2717   fclose(file);
2718
2719   SetFilePermissions(filename, PERMS_PRIVATE);
2720
2721   free(filename);
2722 }
2723
2724 static void checkSeriesInfo()
2725 {
2726   static char *level_directory = NULL;
2727   DIR *dir;
2728   struct dirent *dir_entry;
2729
2730   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2731
2732   level_directory = getPath2((leveldir_current->user_defined ?
2733                               getUserLevelDir(NULL) :
2734                               options.level_directory),
2735                              leveldir_current->fullpath);
2736
2737   if ((dir = opendir(level_directory)) == NULL)
2738   {
2739     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2740     return;
2741   }
2742
2743   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2744   {
2745     if (strlen(dir_entry->d_name) > 4 &&
2746         dir_entry->d_name[3] == '.' &&
2747         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2748     {
2749       char levelnum_str[4];
2750       int levelnum_value;
2751
2752       strncpy(levelnum_str, dir_entry->d_name, 3);
2753       levelnum_str[3] = '\0';
2754
2755       levelnum_value = atoi(levelnum_str);
2756
2757 #if 0
2758       if (levelnum_value < leveldir_current->first_level)
2759       {
2760         Error(ERR_WARN, "additional level %d found", levelnum_value);
2761         leveldir_current->first_level = levelnum_value;
2762       }
2763       else if (levelnum_value > leveldir_current->last_level)
2764       {
2765         Error(ERR_WARN, "additional level %d found", levelnum_value);
2766         leveldir_current->last_level = levelnum_value;
2767       }
2768 #endif
2769     }
2770   }
2771
2772   closedir(dir);
2773 }
2774
2775 void LoadLevelSetup_SeriesInfo()
2776 {
2777   char *filename;
2778   SetupFileHash *level_setup_hash = NULL;
2779   char *level_subdir = leveldir_current->filename;
2780
2781   /* always start with reliable default values */
2782   level_nr = leveldir_current->first_level;
2783
2784   checkSeriesInfo(leveldir_current);
2785
2786   /* ----------------------------------------------------------------------- */
2787   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2788   /* ----------------------------------------------------------------------- */
2789
2790   level_subdir = leveldir_current->filename;
2791
2792   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2793
2794   if ((level_setup_hash = loadSetupFileHash(filename)))
2795   {
2796     char *token_value;
2797
2798     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2799
2800     if (token_value)
2801     {
2802       level_nr = atoi(token_value);
2803
2804       if (level_nr < leveldir_current->first_level)
2805         level_nr = leveldir_current->first_level;
2806       if (level_nr > leveldir_current->last_level)
2807         level_nr = leveldir_current->last_level;
2808     }
2809
2810     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2811
2812     if (token_value)
2813     {
2814       int level_nr = atoi(token_value);
2815
2816       if (level_nr < leveldir_current->first_level)
2817         level_nr = leveldir_current->first_level;
2818       if (level_nr > leveldir_current->last_level + 1)
2819         level_nr = leveldir_current->last_level;
2820
2821       if (leveldir_current->user_defined)
2822         level_nr = leveldir_current->last_level;
2823
2824       leveldir_current->handicap_level = level_nr;
2825     }
2826
2827     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2828
2829     freeSetupFileHash(level_setup_hash);
2830   }
2831   else
2832     Error(ERR_WARN, "using default setup values");
2833
2834   free(filename);
2835 }
2836
2837 void SaveLevelSetup_SeriesInfo()
2838 {
2839   char *filename;
2840   char *level_subdir = leveldir_current->filename;
2841   char *level_nr_str = int2str(level_nr, 0);
2842   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2843   FILE *file;
2844
2845   /* ----------------------------------------------------------------------- */
2846   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2847   /* ----------------------------------------------------------------------- */
2848
2849   InitLevelSetupDirectory(level_subdir);
2850
2851   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2852
2853   if (!(file = fopen(filename, MODE_WRITE)))
2854   {
2855     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2856     free(filename);
2857     return;
2858   }
2859
2860   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2861                                                  getCookie("LEVELSETUP")));
2862   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2863                                                level_nr_str));
2864   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2865                                                handicap_level_str));
2866
2867   fclose(file);
2868
2869   SetFilePermissions(filename, PERMS_PRIVATE);
2870
2871   free(filename);
2872 }