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