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