rnd-20030726-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 /* 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 = getFirstValidTreeInfoEntry(artwork.gfx_first);
2122
2123   artwork.snd_current =
2124     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2125   if (artwork.snd_current == NULL)
2126     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2127
2128   artwork.mus_current =
2129     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2130   if (artwork.mus_current == NULL)
2131     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2132
2133   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2134   artwork.snd_current_identifier = artwork.snd_current->identifier;
2135   artwork.mus_current_identifier = artwork.mus_current->identifier;
2136
2137 #if 0
2138   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2139   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2140   printf("music set == %s\n\n", artwork.mus_current_identifier);
2141 #endif
2142
2143   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2144   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2145   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2146
2147 #if 0
2148   dumpTreeInfo(artwork.gfx_first, 0);
2149   dumpTreeInfo(artwork.snd_first, 0);
2150   dumpTreeInfo(artwork.mus_first, 0);
2151 #endif
2152 }
2153
2154 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2155                                   LevelDirTree *level_node)
2156 {
2157   /* recursively check all level directories for artwork sub-directories */
2158
2159   while (level_node)
2160   {
2161     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2162                           ARTWORK_DIRECTORY((*artwork_node)->type));
2163
2164 #if 0
2165     if (!level_node->parent_link)
2166       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2167              level_node->filename, level_node->name);
2168 #endif
2169
2170     if (!level_node->parent_link)
2171     {
2172       TreeInfo *topnode_last = *artwork_node;
2173
2174       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2175                                     (*artwork_node)->type);
2176
2177       if (topnode_last != *artwork_node)
2178       {
2179         free((*artwork_node)->identifier);
2180         free((*artwork_node)->name);
2181         free((*artwork_node)->name_sorting);
2182
2183         (*artwork_node)->identifier   = getStringCopy(level_node->filename);
2184         (*artwork_node)->name         = getStringCopy(level_node->name);
2185         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2186
2187         (*artwork_node)->sort_priority = level_node->sort_priority;
2188         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2189       }
2190     }
2191
2192     free(path);
2193
2194     if (level_node->node_group != NULL)
2195       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2196
2197     level_node = level_node->next;
2198   }
2199 }
2200
2201 void LoadLevelArtworkInfo()
2202 {
2203   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2204
2205   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2206   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2207   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2208
2209   /* needed for reloading level artwork not known at ealier stage */
2210   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2211   {
2212     artwork.gfx_current =
2213       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2214     if (artwork.gfx_current == NULL)
2215       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2216   }
2217
2218   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2219   {
2220     artwork.snd_current =
2221       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2222     if (artwork.snd_current == NULL)
2223       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2224   }
2225
2226   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2227   {
2228     artwork.mus_current =
2229       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2230     if (artwork.mus_current == NULL)
2231       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2232   }
2233
2234   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2235   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2236   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2237
2238 #if 0
2239   dumpTreeInfo(artwork.gfx_first, 0);
2240   dumpTreeInfo(artwork.snd_first, 0);
2241   dumpTreeInfo(artwork.mus_first, 0);
2242 #endif
2243 }
2244
2245 static void SaveUserLevelInfo()
2246 {
2247   char *filename;
2248   FILE *file;
2249   int i;
2250
2251   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2252
2253   if (!(file = fopen(filename, MODE_WRITE)))
2254   {
2255     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2256     free(filename);
2257     return;
2258   }
2259
2260   /* always start with reliable default values */
2261   setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
2262
2263   ldi.name = getStringCopy(getLoginName());
2264   ldi.author = getStringCopy(getRealName());
2265   ldi.levels = 100;
2266   ldi.first_level = 1;
2267   ldi.sort_priority = LEVELCLASS_USER_START;
2268   ldi.readonly = FALSE;
2269   ldi.graphics_set = getStringCopy(GRAPHICS_SUBDIR);
2270   ldi.sounds_set = getStringCopy(SOUNDS_SUBDIR);
2271   ldi.music_set = getStringCopy(MUSIC_SUBDIR);
2272
2273   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2274                                                  getCookie("LEVELINFO")));
2275
2276   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2277     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2278         i != LEVELINFO_TOKEN_NAME_SORTING &&
2279         i != LEVELINFO_TOKEN_IMPORTED_FROM)
2280       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2281
2282   fclose(file);
2283   free(filename);
2284
2285   SetFilePermissions(filename, PERMS_PRIVATE);
2286 }
2287
2288 char *getSetupValue(int type, void *value)
2289 {
2290   static char value_string[MAX_LINE_LEN];
2291
2292   if (value == NULL)
2293     return NULL;
2294
2295   switch (type)
2296   {
2297     case TYPE_BOOLEAN:
2298       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2299       break;
2300
2301     case TYPE_SWITCH:
2302       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2303       break;
2304
2305     case TYPE_YES_NO:
2306       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2307       break;
2308
2309     case TYPE_KEY:
2310       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2311       break;
2312
2313     case TYPE_KEY_X11:
2314       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2315       break;
2316
2317     case TYPE_INTEGER:
2318       sprintf(value_string, "%d", *(int *)value);
2319       break;
2320
2321     case TYPE_STRING:
2322       strcpy(value_string, *(char **)value);
2323       break;
2324
2325     default:
2326       value_string[0] = '\0';
2327       break;
2328   }
2329
2330   return value_string;
2331 }
2332
2333 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2334 {
2335   int i;
2336   char *line;
2337   static char token_string[MAX_LINE_LEN];
2338   int token_type = token_info[token_nr].type;
2339   void *setup_value = token_info[token_nr].value;
2340   char *token_text = token_info[token_nr].text;
2341   char *value_string = getSetupValue(token_type, setup_value);
2342
2343   /* build complete token string */
2344   sprintf(token_string, "%s%s", prefix, token_text);
2345
2346   /* build setup entry line */
2347   line = getFormattedSetupEntry(token_string, value_string);
2348
2349   if (token_type == TYPE_KEY_X11)
2350   {
2351     Key key = *(Key *)setup_value;
2352     char *keyname = getKeyNameFromKey(key);
2353
2354     /* add comment, if useful */
2355     if (strcmp(keyname, "(undefined)") != 0 &&
2356         strcmp(keyname, "(unknown)") != 0)
2357     {
2358       /* add at least one whitespace */
2359       strcat(line, " ");
2360       for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2361         strcat(line, " ");
2362
2363       strcat(line, "# ");
2364       strcat(line, keyname);
2365     }
2366   }
2367
2368   return line;
2369 }
2370
2371 void LoadLevelSetup_LastSeries()
2372 {
2373   char *filename;
2374   SetupFileHash *level_setup_hash = NULL;
2375
2376   /* always start with reliable default values */
2377   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2378
2379   /* ----------------------------------------------------------------------- */
2380   /* ~/.<program>/levelsetup.conf                                            */
2381   /* ----------------------------------------------------------------------- */
2382
2383   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2384
2385   if ((level_setup_hash = loadSetupFileHash(filename)))
2386   {
2387     char *last_level_series =
2388       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2389
2390     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2391                                                  last_level_series);
2392     if (leveldir_current == NULL)
2393       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2394
2395     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2396
2397     freeSetupFileHash(level_setup_hash);
2398   }
2399   else
2400     Error(ERR_WARN, "using default setup values");
2401
2402   free(filename);
2403 }
2404
2405 void SaveLevelSetup_LastSeries()
2406 {
2407   char *filename;
2408   char *level_subdir = leveldir_current->filename;
2409   FILE *file;
2410
2411   /* ----------------------------------------------------------------------- */
2412   /* ~/.<program>/levelsetup.conf                                            */
2413   /* ----------------------------------------------------------------------- */
2414
2415   InitUserDataDirectory();
2416
2417   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2418
2419   if (!(file = fopen(filename, MODE_WRITE)))
2420   {
2421     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2422     free(filename);
2423     return;
2424   }
2425
2426   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2427                                                  getCookie("LEVELSETUP")));
2428   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2429                                                level_subdir));
2430
2431   fclose(file);
2432   free(filename);
2433
2434   SetFilePermissions(filename, PERMS_PRIVATE);
2435 }
2436
2437 static void checkSeriesInfo()
2438 {
2439   static char *level_directory = NULL;
2440   DIR *dir;
2441   struct dirent *dir_entry;
2442
2443   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2444
2445   level_directory = getPath2((leveldir_current->user_defined ?
2446                               getUserLevelDir(NULL) :
2447                               options.level_directory),
2448                              leveldir_current->fullpath);
2449
2450   if ((dir = opendir(level_directory)) == NULL)
2451   {
2452     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2453     return;
2454   }
2455
2456   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2457   {
2458     if (strlen(dir_entry->d_name) > 4 &&
2459         dir_entry->d_name[3] == '.' &&
2460         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2461     {
2462       char levelnum_str[4];
2463       int levelnum_value;
2464
2465       strncpy(levelnum_str, dir_entry->d_name, 3);
2466       levelnum_str[3] = '\0';
2467
2468       levelnum_value = atoi(levelnum_str);
2469
2470 #if 0
2471       if (levelnum_value < leveldir_current->first_level)
2472       {
2473         Error(ERR_WARN, "additional level %d found", levelnum_value);
2474         leveldir_current->first_level = levelnum_value;
2475       }
2476       else if (levelnum_value > leveldir_current->last_level)
2477       {
2478         Error(ERR_WARN, "additional level %d found", levelnum_value);
2479         leveldir_current->last_level = levelnum_value;
2480       }
2481 #endif
2482     }
2483   }
2484
2485   closedir(dir);
2486 }
2487
2488 void LoadLevelSetup_SeriesInfo()
2489 {
2490   char *filename;
2491   SetupFileHash *level_setup_hash = NULL;
2492   char *level_subdir = leveldir_current->filename;
2493
2494   /* always start with reliable default values */
2495   level_nr = leveldir_current->first_level;
2496
2497   checkSeriesInfo(leveldir_current);
2498
2499   /* ----------------------------------------------------------------------- */
2500   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2501   /* ----------------------------------------------------------------------- */
2502
2503   level_subdir = leveldir_current->filename;
2504
2505   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2506
2507   if ((level_setup_hash = loadSetupFileHash(filename)))
2508   {
2509     char *token_value;
2510
2511     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2512
2513     if (token_value)
2514     {
2515       level_nr = atoi(token_value);
2516
2517       if (level_nr < leveldir_current->first_level)
2518         level_nr = leveldir_current->first_level;
2519       if (level_nr > leveldir_current->last_level)
2520         level_nr = leveldir_current->last_level;
2521     }
2522
2523     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2524
2525     if (token_value)
2526     {
2527       int level_nr = atoi(token_value);
2528
2529       if (level_nr < leveldir_current->first_level)
2530         level_nr = leveldir_current->first_level;
2531       if (level_nr > leveldir_current->last_level + 1)
2532         level_nr = leveldir_current->last_level;
2533
2534       if (leveldir_current->user_defined)
2535         level_nr = leveldir_current->last_level;
2536
2537       leveldir_current->handicap_level = level_nr;
2538     }
2539
2540     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2541
2542     freeSetupFileHash(level_setup_hash);
2543   }
2544   else
2545     Error(ERR_WARN, "using default setup values");
2546
2547   free(filename);
2548 }
2549
2550 void SaveLevelSetup_SeriesInfo()
2551 {
2552   char *filename;
2553   char *level_subdir = leveldir_current->filename;
2554   char *level_nr_str = int2str(level_nr, 0);
2555   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2556   FILE *file;
2557
2558   /* ----------------------------------------------------------------------- */
2559   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2560   /* ----------------------------------------------------------------------- */
2561
2562   InitLevelSetupDirectory(level_subdir);
2563
2564   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2565
2566   if (!(file = fopen(filename, MODE_WRITE)))
2567   {
2568     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2569     free(filename);
2570     return;
2571   }
2572
2573   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2574                                                  getCookie("LEVELSETUP")));
2575   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2576                                                level_nr_str));
2577   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2578                                                handicap_level_str));
2579
2580   fclose(file);
2581   free(filename);
2582
2583   SetFilePermissions(filename, PERMS_PRIVATE);
2584 }