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