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