rnd-20030428-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 SetupFileList *newSetupFileList(char *token, char *value)
1044 {
1045   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1046
1047   new->token = getStringCopy(token);
1048   new->value = getStringCopy(value);
1049
1050   new->next = NULL;
1051
1052   return new;
1053 }
1054
1055 void freeSetupFileList(SetupFileList *list)
1056 {
1057   if (list == NULL)
1058     return;
1059
1060   if (list->token)
1061     free(list->token);
1062   if (list->value)
1063     free(list->value);
1064   if (list->next)
1065     freeSetupFileList(list->next);
1066   free(list);
1067 }
1068
1069 char *getListEntry(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 getListEntry(list->next, token);
1078 }
1079
1080 void setListEntry(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     setListEntry(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     If you just want to have a good hash function, and cannot wait, djb2
1135     is one of the best string hash functions i know. It has excellent
1136     distribution and speed on many different sets of keys and table sizes.
1137     You are not likely to do better with one of the "well known" functions
1138     such as PJW, K&R, etc.
1139
1140     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1141   */
1142
1143   char *str = (char *)key;
1144   unsigned int hash = 5381;
1145   int c;
1146
1147   while ((c = *str++))
1148     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1149
1150   return hash;
1151 }
1152
1153 static int keys_are_equal(void *key1, void *key2)
1154 {
1155   return (strcmp((char *)key1, (char *)key2) == 0);
1156 }
1157
1158 SetupFileHash *newSetupFileHash()
1159 {
1160   SetupFileHash *new_hash =
1161     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1162
1163   return new_hash;
1164 }
1165
1166 void freeSetupFileHash(SetupFileHash *hash)
1167 {
1168   if (hash == NULL)
1169     return;
1170
1171   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1172 }
1173
1174 char *getHashEntry(SetupFileHash *hash, char *token)
1175 {
1176   if (hash == NULL)
1177     return NULL;
1178
1179   return search_hash_entry(hash, token);
1180 }
1181
1182 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1183 {
1184   char *value_copy;
1185
1186   if (hash == NULL)
1187     return;
1188
1189   value_copy = getStringCopy(value);
1190
1191   /* change value; if it does not exist, insert it as new */
1192   if (!change_hash_entry(hash, token, value_copy))
1193     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1194       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1195 }
1196
1197 #if 0
1198 #ifdef DEBUG
1199 static void printSetupFileHash(SetupFileHash *hash)
1200 {
1201   BEGIN_HASH_ITERATION(hash, itr)
1202   {
1203     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1204     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1205   }
1206   END_HASH_ITERATION(hash, itr)
1207 }
1208 #endif
1209 #endif
1210
1211 static void *loadSetupFileData(char *filename, boolean use_hash)
1212 {
1213   int line_len;
1214   char line[MAX_LINE_LEN];
1215   char *token, *value, *line_ptr;
1216   void *setup_file_data;
1217   FILE *file;
1218
1219   if (use_hash)
1220     setup_file_data = newSetupFileHash();
1221   else
1222     setup_file_data = newSetupFileList("", "");
1223
1224   if (!(file = fopen(filename, MODE_READ)))
1225   {
1226     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1227     return NULL;
1228   }
1229
1230   while(!feof(file))
1231   {
1232     /* read next line of input file */
1233     if (!fgets(line, MAX_LINE_LEN, file))
1234       break;
1235
1236     /* cut trailing comment or whitespace from input line */
1237     for (line_ptr = line; *line_ptr; line_ptr++)
1238     {
1239       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1240       {
1241         *line_ptr = '\0';
1242         break;
1243       }
1244     }
1245
1246     /* cut trailing whitespaces from input line */
1247     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1248       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1249         *line_ptr = '\0';
1250
1251     /* ignore empty lines */
1252     if (*line == '\0')
1253       continue;
1254
1255     line_len = strlen(line);
1256
1257     /* cut leading whitespaces from token */
1258     for (token = line; *token; token++)
1259       if (*token != ' ' && *token != '\t')
1260         break;
1261
1262     /* find end of token */
1263     for (line_ptr = token; *line_ptr; line_ptr++)
1264     {
1265       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1266       {
1267         *line_ptr = '\0';
1268         break;
1269       }
1270     }
1271
1272     if (line_ptr < line + line_len)
1273       value = line_ptr + 1;
1274     else
1275       value = "\0";
1276
1277     /* cut leading whitespaces from value */
1278     for (; *value; value++)
1279       if (*value != ' ' && *value != '\t')
1280         break;
1281
1282     if (*token && *value)
1283     {
1284       if (use_hash)
1285         setHashEntry((SetupFileHash *)setup_file_data, token, value);
1286       else
1287         setListEntry((SetupFileList *)setup_file_data, token, value);
1288     }
1289   }
1290
1291   fclose(file);
1292
1293   if (use_hash)
1294   {
1295     if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1296       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1297   }
1298   else
1299   {
1300     SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1301     SetupFileList *first_valid_list_entry = setup_file_list->next;
1302
1303     /* free empty list header */
1304     setup_file_list->next = NULL;
1305     freeSetupFileList(setup_file_list);
1306     setup_file_data = first_valid_list_entry;
1307
1308     if (first_valid_list_entry == NULL)
1309       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1310   }
1311
1312   return setup_file_data;
1313 }
1314
1315 SetupFileList *loadSetupFileList(char *filename)
1316 {
1317   return (SetupFileList *)loadSetupFileData(filename, FALSE);
1318 }
1319
1320 SetupFileHash *loadSetupFileHash(char *filename)
1321 {
1322   return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1323 }
1324
1325 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1326                                   char *identifier)
1327 {
1328   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1329
1330   if (value == NULL)
1331     Error(ERR_WARN, "configuration file has no file identifier");
1332   else if (!checkCookieString(value, identifier))
1333     Error(ERR_WARN, "configuration file has wrong file identifier");
1334 }
1335
1336
1337 /* ========================================================================= */
1338 /* setup file stuff                                                          */
1339 /* ========================================================================= */
1340
1341 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1342 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1343 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1344
1345 /* level directory info */
1346 #define LEVELINFO_TOKEN_IDENTIFIER      0
1347 #define LEVELINFO_TOKEN_NAME            1
1348 #define LEVELINFO_TOKEN_NAME_SORTING    2
1349 #define LEVELINFO_TOKEN_AUTHOR          3
1350 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1351 #define LEVELINFO_TOKEN_LEVELS          5
1352 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
1353 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
1354 #define LEVELINFO_TOKEN_LEVEL_GROUP     8
1355 #define LEVELINFO_TOKEN_READONLY        9
1356 #define LEVELINFO_TOKEN_GRAPHICS_SET    10
1357 #define LEVELINFO_TOKEN_SOUNDS_SET      11
1358 #define LEVELINFO_TOKEN_MUSIC_SET       12
1359
1360 #define NUM_LEVELINFO_TOKENS            13
1361
1362 static LevelDirTree ldi;
1363
1364 static struct TokenInfo levelinfo_tokens[] =
1365 {
1366   /* level directory info */
1367   { TYPE_STRING,  &ldi.identifier,      "identifier"    },
1368   { TYPE_STRING,  &ldi.name,            "name"          },
1369   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1370   { TYPE_STRING,  &ldi.author,          "author"        },
1371   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1372   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1373   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1374   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1375   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1376   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      },
1377   { TYPE_STRING,  &ldi.graphics_set,    "graphics_set"  },
1378   { TYPE_STRING,  &ldi.sounds_set,      "sounds_set"    },
1379   { TYPE_STRING,  &ldi.music_set,       "music_set"     }
1380 };
1381
1382 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1383 {
1384   ldi->type = type;
1385
1386   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1387                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1388                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1389                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1390                    NULL);
1391
1392   ldi->node_parent = NULL;
1393   ldi->node_group = NULL;
1394   ldi->next = NULL;
1395
1396   ldi->cl_first = -1;
1397   ldi->cl_cursor = -1;
1398
1399   ldi->filename = NULL;
1400   ldi->fullpath = NULL;
1401   ldi->basepath = NULL;
1402   ldi->identifier = NULL;
1403   ldi->name = getStringCopy(ANONYMOUS_NAME);
1404   ldi->name_sorting = NULL;
1405   ldi->author = getStringCopy(ANONYMOUS_NAME);
1406
1407   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1408   ldi->parent_link = FALSE;
1409   ldi->user_defined = FALSE;
1410   ldi->color = 0;
1411   ldi->class_desc = NULL;
1412
1413   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1414   {
1415     ldi->imported_from = NULL;
1416     ldi->graphics_set = NULL;
1417     ldi->sounds_set = NULL;
1418     ldi->music_set = NULL;
1419     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1420     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1421     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1422     ldi->levels = 0;
1423     ldi->first_level = 0;
1424     ldi->last_level = 0;
1425     ldi->level_group = FALSE;
1426     ldi->handicap_level = 0;
1427     ldi->readonly = TRUE;
1428   }
1429 }
1430
1431 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1432 {
1433   if (parent == NULL)
1434   {
1435     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1436
1437     setTreeInfoToDefaults(ldi, TREE_TYPE_GENERIC);
1438     return;
1439   }
1440
1441   /* first copy all values from the parent structure ... */
1442   *ldi = *parent;
1443
1444   /* ... then set all fields to default that cannot be inherited from parent.
1445      This is especially important for all those fields that can be set from
1446      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1447      calls 'free()' for all already set token values which requires that no
1448      other structure's pointer may point to them!
1449   */
1450
1451   ldi->filename = NULL;
1452   ldi->fullpath = NULL;
1453   ldi->basepath = NULL;
1454   ldi->identifier = NULL;
1455   ldi->name = getStringCopy(ANONYMOUS_NAME);
1456   ldi->name_sorting = NULL;
1457   ldi->author = getStringCopy(parent->author);
1458   ldi->imported_from = getStringCopy(parent->imported_from);
1459
1460   ldi->level_group = FALSE;
1461   ldi->parent_link = FALSE;
1462
1463   ldi->node_top = parent->node_top;
1464   ldi->node_parent = parent;
1465   ldi->node_group = NULL;
1466   ldi->next = NULL;
1467 }
1468
1469 void setSetupInfo(struct TokenInfo *token_info,
1470                   int token_nr, char *token_value)
1471 {
1472   int token_type = token_info[token_nr].type;
1473   void *setup_value = token_info[token_nr].value;
1474
1475   if (token_value == NULL)
1476     return;
1477
1478   /* set setup field to corresponding token value */
1479   switch (token_type)
1480   {
1481     case TYPE_BOOLEAN:
1482     case TYPE_SWITCH:
1483       *(boolean *)setup_value = get_boolean_from_string(token_value);
1484       break;
1485
1486     case TYPE_KEY:
1487       *(Key *)setup_value = getKeyFromKeyName(token_value);
1488       break;
1489
1490     case TYPE_KEY_X11:
1491       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1492       break;
1493
1494     case TYPE_INTEGER:
1495       *(int *)setup_value = get_integer_from_string(token_value);
1496       break;
1497
1498     case TYPE_STRING:
1499       if (*(char **)setup_value != NULL)
1500         free(*(char **)setup_value);
1501       *(char **)setup_value = getStringCopy(token_value);
1502       break;
1503
1504     default:
1505       break;
1506   }
1507 }
1508
1509 static int compareTreeInfoEntries(const void *object1, const void *object2)
1510 {
1511   const TreeInfo *entry1 = *((TreeInfo **)object1);
1512   const TreeInfo *entry2 = *((TreeInfo **)object2);
1513   int class_sorting1, class_sorting2;
1514   int compare_result;
1515
1516   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1517   {
1518     class_sorting1 = LEVELSORTING(entry1);
1519     class_sorting2 = LEVELSORTING(entry2);
1520   }
1521   else
1522   {
1523     class_sorting1 = ARTWORKSORTING(entry1);
1524     class_sorting2 = ARTWORKSORTING(entry2);
1525   }
1526
1527   if (entry1->parent_link || entry2->parent_link)
1528     compare_result = (entry1->parent_link ? -1 : +1);
1529   else if (entry1->sort_priority == entry2->sort_priority)
1530   {
1531     char *name1 = getStringToLower(entry1->name_sorting);
1532     char *name2 = getStringToLower(entry2->name_sorting);
1533
1534     compare_result = strcmp(name1, name2);
1535
1536     free(name1);
1537     free(name2);
1538   }
1539   else if (class_sorting1 == class_sorting2)
1540     compare_result = entry1->sort_priority - entry2->sort_priority;
1541   else
1542     compare_result = class_sorting1 - class_sorting2;
1543
1544   return compare_result;
1545 }
1546
1547 static void createParentTreeInfoNode(TreeInfo *node_parent)
1548 {
1549   TreeInfo *ti_new;
1550
1551   if (node_parent == NULL)
1552     return;
1553
1554   ti_new = newTreeInfo();
1555   setTreeInfoToDefaults(ti_new, node_parent->type);
1556
1557   ti_new->node_parent = node_parent;
1558   ti_new->parent_link = TRUE;
1559
1560   ti_new->identifier = getStringCopy(node_parent->identifier);
1561   ti_new->name = ".. (parent directory)";
1562   ti_new->name_sorting = getStringCopy(ti_new->name);
1563
1564   ti_new->filename = "..";
1565   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1566
1567   ti_new->sort_priority = node_parent->sort_priority;
1568   ti_new->class_desc = getLevelClassDescription(ti_new);
1569
1570   pushTreeInfo(&node_parent->node_group, ti_new);
1571 }
1572
1573 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1574 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1575
1576 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1577                                           TreeInfo *node_parent,
1578                                           char *level_directory,
1579                                           char *directory_name)
1580 {
1581   char *directory_path = getPath2(level_directory, directory_name);
1582   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1583   SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1584   LevelDirTree *leveldir_new = NULL;
1585   int i;
1586
1587   if (setup_file_hash == NULL)
1588   {
1589     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1590
1591     free(directory_path);
1592     free(filename);
1593
1594     return FALSE;
1595   }
1596
1597   leveldir_new = newTreeInfo();
1598
1599   if (node_parent)
1600     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1601   else
1602     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1603
1604   leveldir_new->filename = getStringCopy(directory_name);
1605
1606   checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1607
1608   /* set all structure fields according to the token/value pairs */
1609   ldi = *leveldir_new;
1610   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1611     setSetupInfo(levelinfo_tokens, i,
1612                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1613   *leveldir_new = ldi;
1614
1615   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1616   {
1617     free(leveldir_new->name);
1618     leveldir_new->name = getStringCopy(leveldir_new->filename);
1619   }
1620
1621   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1622
1623   if (leveldir_new->identifier == NULL)
1624     leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1625
1626   if (leveldir_new->name_sorting == NULL)
1627     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1628
1629   if (node_parent == NULL)              /* top level group */
1630   {
1631     leveldir_new->basepath = level_directory;
1632     leveldir_new->fullpath = leveldir_new->filename;
1633   }
1634   else                                  /* sub level group */
1635   {
1636     leveldir_new->basepath = node_parent->basepath;
1637     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1638   }
1639
1640   if (leveldir_new->levels < 1)
1641     leveldir_new->levels = 1;
1642
1643   leveldir_new->last_level =
1644     leveldir_new->first_level + leveldir_new->levels - 1;
1645
1646   leveldir_new->user_defined =
1647     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1648
1649   leveldir_new->color = LEVELCOLOR(leveldir_new);
1650   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1651
1652   leveldir_new->handicap_level =        /* set handicap to default value */
1653     (leveldir_new->user_defined ?
1654      leveldir_new->last_level :
1655      leveldir_new->first_level);
1656
1657   pushTreeInfo(node_first, leveldir_new);
1658
1659   freeSetupFileHash(setup_file_hash);
1660
1661   if (leveldir_new->level_group)
1662   {
1663     /* create node to link back to current level directory */
1664     createParentTreeInfoNode(leveldir_new);
1665
1666     /* step into sub-directory and look for more level series */
1667     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1668                               leveldir_new, directory_path);
1669   }
1670
1671   free(directory_path);
1672   free(filename);
1673
1674   return TRUE;
1675 }
1676
1677 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1678                                       TreeInfo *node_parent,
1679                                       char *level_directory)
1680 {
1681   DIR *dir;
1682   struct dirent *dir_entry;
1683   boolean valid_entry_found = FALSE;
1684
1685   if ((dir = opendir(level_directory)) == NULL)
1686   {
1687     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1688     return;
1689   }
1690
1691   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1692   {
1693     struct stat file_status;
1694     char *directory_name = dir_entry->d_name;
1695     char *directory_path = getPath2(level_directory, directory_name);
1696
1697     /* skip entries for current and parent directory */
1698     if (strcmp(directory_name, ".")  == 0 ||
1699         strcmp(directory_name, "..") == 0)
1700     {
1701       free(directory_path);
1702       continue;
1703     }
1704
1705     /* find out if directory entry is itself a directory */
1706     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1707         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1708     {
1709       free(directory_path);
1710       continue;
1711     }
1712
1713     free(directory_path);
1714
1715     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
1716         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
1717         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
1718       continue;
1719
1720     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1721                                                     level_directory,
1722                                                     directory_name);
1723   }
1724
1725   closedir(dir);
1726
1727   if (!valid_entry_found)
1728   {
1729     /* check if this directory directly contains a file "levelinfo.conf" */
1730     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1731                                                     level_directory, ".");
1732   }
1733
1734   if (!valid_entry_found)
1735     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1736           level_directory);
1737 }
1738
1739 void LoadLevelInfo()
1740 {
1741   InitUserLevelDirectory(getLoginName());
1742
1743   DrawInitText("Loading level series:", 120, FC_GREEN);
1744
1745   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1746   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1747
1748   /* before sorting, the first entries will be from the user directory */
1749   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1750
1751   if (leveldir_first == NULL)
1752     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1753
1754   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1755
1756 #if 0
1757   dumpTreeInfo(leveldir_first, 0);
1758 #endif
1759 }
1760
1761 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1762                                               TreeInfo *node_parent,
1763                                               char *base_directory,
1764                                               char *directory_name, int type)
1765 {
1766   char *directory_path = getPath2(base_directory, directory_name);
1767   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
1768   SetupFileHash *setup_file_hash = NULL;
1769   TreeInfo *artwork_new = NULL;
1770   int i;
1771
1772   if (access(filename, F_OK) == 0)              /* file exists */
1773     setup_file_hash = loadSetupFileHash(filename);
1774
1775   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
1776   {
1777     DIR *dir;
1778     struct dirent *dir_entry;
1779     boolean valid_file_found = FALSE;
1780
1781     if ((dir = opendir(directory_path)) != NULL)
1782     {
1783       while ((dir_entry = readdir(dir)) != NULL)
1784       {
1785         char *entry_name = dir_entry->d_name;
1786
1787         if (FileIsArtworkType(entry_name, type))
1788         {
1789           valid_file_found = TRUE;
1790           break;
1791         }
1792       }
1793
1794       closedir(dir);
1795     }
1796
1797     if (!valid_file_found)
1798     {
1799       if (strcmp(directory_name, ".") != 0)
1800         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
1801
1802       free(directory_path);
1803       free(filename);
1804
1805       return FALSE;
1806     }
1807   }
1808
1809   artwork_new = newTreeInfo();
1810
1811   if (node_parent)
1812     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1813   else
1814     setTreeInfoToDefaults(artwork_new, type);
1815
1816   artwork_new->filename = getStringCopy(directory_name);
1817
1818   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
1819   {
1820 #if 0
1821     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
1822 #endif
1823
1824     /* set all structure fields according to the token/value pairs */
1825     ldi = *artwork_new;
1826     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1827       setSetupInfo(levelinfo_tokens, i,
1828                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1829     *artwork_new = ldi;
1830
1831     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
1832     {
1833       free(artwork_new->name);
1834       artwork_new->name = getStringCopy(artwork_new->filename);
1835     }
1836
1837 #if 0
1838     DrawInitText(artwork_new->name, 150, FC_YELLOW);
1839 #endif
1840
1841     if (artwork_new->identifier == NULL)
1842       artwork_new->identifier = getStringCopy(artwork_new->filename);
1843
1844     if (artwork_new->name_sorting == NULL)
1845       artwork_new->name_sorting = getStringCopy(artwork_new->name);
1846   }
1847
1848   if (node_parent == NULL)              /* top level group */
1849   {
1850     artwork_new->basepath = getStringCopy(base_directory);
1851     artwork_new->fullpath = getStringCopy(artwork_new->filename);
1852   }
1853   else                                  /* sub level group */
1854   {
1855     artwork_new->basepath = getStringCopy(node_parent->basepath);
1856     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1857   }
1858
1859   artwork_new->user_defined =
1860     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
1861
1862   /* (may use ".sort_priority" from "setup_file_hash" above) */
1863   artwork_new->color = ARTWORKCOLOR(artwork_new);
1864   artwork_new->class_desc = getLevelClassDescription(artwork_new);
1865
1866   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
1867   {
1868     if (artwork_new->name != NULL)
1869       free(artwork_new->name);
1870
1871     if (strcmp(artwork_new->filename, ".") == 0)
1872     {
1873       if (artwork_new->user_defined)
1874       {
1875         artwork_new->identifier = getStringCopy("private");
1876         artwork_new->sort_priority = ARTWORKCLASS_USER;
1877       }
1878       else
1879       {
1880         artwork_new->identifier = getStringCopy("classic");
1881         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
1882       }
1883
1884       /* set to new values after changing ".sort_priority" */
1885       artwork_new->color = ARTWORKCOLOR(artwork_new);
1886       artwork_new->class_desc = getLevelClassDescription(artwork_new);
1887     }
1888     else
1889     {
1890       artwork_new->identifier = getStringCopy(artwork_new->filename);
1891     }
1892
1893     artwork_new->name = getStringCopy(artwork_new->identifier);
1894     artwork_new->name_sorting = getStringCopy(artwork_new->name);
1895   }
1896
1897   DrawInitText(artwork_new->name, 150, FC_YELLOW);
1898
1899   pushTreeInfo(node_first, artwork_new);
1900
1901   freeSetupFileHash(setup_file_hash);
1902
1903   free(directory_path);
1904   free(filename);
1905
1906   return TRUE;
1907 }
1908
1909 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
1910                                           TreeInfo *node_parent,
1911                                           char *base_directory, int type)
1912 {
1913   DIR *dir;
1914   struct dirent *dir_entry;
1915   boolean valid_entry_found = FALSE;
1916
1917   if ((dir = opendir(base_directory)) == NULL)
1918   {
1919     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
1920       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
1921     return;
1922   }
1923
1924   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1925   {
1926     struct stat file_status;
1927     char *directory_name = dir_entry->d_name;
1928     char *directory_path = getPath2(base_directory, directory_name);
1929
1930     /* skip entries for current and parent directory */
1931     if (strcmp(directory_name, ".")  == 0 ||
1932         strcmp(directory_name, "..") == 0)
1933     {
1934       free(directory_path);
1935       continue;
1936     }
1937
1938     /* find out if directory entry is itself a directory */
1939     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1940         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1941     {
1942       free(directory_path);
1943       continue;
1944     }
1945
1946     free(directory_path);
1947
1948     /* check if this directory contains artwork with or without config file */
1949     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1950                                                         base_directory,
1951                                                         directory_name, type);
1952   }
1953
1954   closedir(dir);
1955
1956   /* check if this directory directly contains artwork itself */
1957   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1958                                                       base_directory, ".",
1959                                                       type);
1960   if (!valid_entry_found)
1961     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
1962           base_directory);
1963 }
1964
1965 static TreeInfo *getDummyArtworkInfo(int type)
1966 {
1967   /* this is only needed when there is completely no artwork available */
1968   TreeInfo *artwork_new = newTreeInfo();
1969
1970   setTreeInfoToDefaults(artwork_new, type);
1971
1972   artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
1973   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
1974   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
1975
1976   if (artwork_new->name != NULL)
1977     free(artwork_new->name);
1978
1979   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
1980   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
1981   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
1982
1983   return artwork_new;
1984 }
1985
1986 void LoadArtworkInfo()
1987 {
1988   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
1989
1990   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1991                                 options.graphics_directory,
1992                                 TREE_TYPE_GRAPHICS_DIR);
1993   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1994                                 getUserGraphicsDir(),
1995                                 TREE_TYPE_GRAPHICS_DIR);
1996
1997   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1998                                 options.sounds_directory,
1999                                 TREE_TYPE_SOUNDS_DIR);
2000   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2001                                 getUserSoundsDir(),
2002                                 TREE_TYPE_SOUNDS_DIR);
2003
2004   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2005                                 options.music_directory,
2006                                 TREE_TYPE_MUSIC_DIR);
2007   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2008                                 getUserMusicDir(),
2009                                 TREE_TYPE_MUSIC_DIR);
2010
2011   if (artwork.gfx_first == NULL)
2012     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2013   if (artwork.snd_first == NULL)
2014     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2015   if (artwork.mus_first == NULL)
2016     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2017
2018   /* before sorting, the first entries will be from the user directory */
2019   artwork.gfx_current =
2020     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2021   if (artwork.gfx_current == NULL)
2022     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2023
2024   artwork.snd_current =
2025     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2026   if (artwork.snd_current == NULL)
2027     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2028
2029   artwork.mus_current =
2030     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2031   if (artwork.mus_current == NULL)
2032     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2033
2034   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2035   artwork.snd_current_identifier = artwork.snd_current->identifier;
2036   artwork.mus_current_identifier = artwork.mus_current->identifier;
2037
2038 #if 0
2039   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2040   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2041   printf("music set == %s\n\n", artwork.mus_current_identifier);
2042 #endif
2043
2044   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2045   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2046   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2047
2048 #if 0
2049   dumpTreeInfo(artwork.gfx_first, 0);
2050   dumpTreeInfo(artwork.snd_first, 0);
2051   dumpTreeInfo(artwork.mus_first, 0);
2052 #endif
2053 }
2054
2055 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2056                                   LevelDirTree *level_node)
2057 {
2058   /* recursively check all level directories for artwork sub-directories */
2059
2060   while (level_node)
2061   {
2062     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2063                           ARTWORK_DIRECTORY((*artwork_node)->type));
2064
2065 #if 0
2066     if (!level_node->parent_link)
2067       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2068              level_node->filename, level_node->name);
2069 #endif
2070
2071     if (!level_node->parent_link)
2072     {
2073       TreeInfo *topnode_last = *artwork_node;
2074
2075       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2076                                     (*artwork_node)->type);
2077
2078       if (topnode_last != *artwork_node)
2079       {
2080         free((*artwork_node)->identifier);
2081         free((*artwork_node)->name);
2082         free((*artwork_node)->name_sorting);
2083
2084         (*artwork_node)->identifier   = getStringCopy(level_node->filename);
2085         (*artwork_node)->name         = getStringCopy(level_node->name);
2086         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2087
2088         (*artwork_node)->sort_priority = level_node->sort_priority;
2089         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2090       }
2091     }
2092
2093     free(path);
2094
2095     if (level_node->node_group != NULL)
2096       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2097
2098     level_node = level_node->next;
2099   }
2100 }
2101
2102 void LoadLevelArtworkInfo()
2103 {
2104   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2105
2106   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2107   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2108   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2109
2110   /* needed for reloading level artwork not known at ealier stage */
2111   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2112   {
2113     artwork.gfx_current =
2114       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2115     if (artwork.gfx_current == NULL)
2116       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2117   }
2118
2119   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2120   {
2121     artwork.snd_current =
2122       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2123     if (artwork.snd_current == NULL)
2124       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2125   }
2126
2127   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2128   {
2129     artwork.mus_current =
2130       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2131     if (artwork.mus_current == NULL)
2132       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2133   }
2134
2135   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2136   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2137   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2138
2139 #if 0
2140   dumpTreeInfo(artwork.gfx_first, 0);
2141   dumpTreeInfo(artwork.snd_first, 0);
2142   dumpTreeInfo(artwork.mus_first, 0);
2143 #endif
2144 }
2145
2146 static void SaveUserLevelInfo()
2147 {
2148   char *filename;
2149   FILE *file;
2150   int i;
2151
2152   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2153
2154   if (!(file = fopen(filename, MODE_WRITE)))
2155   {
2156     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2157     free(filename);
2158     return;
2159   }
2160
2161   /* always start with reliable default values */
2162   setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
2163
2164   ldi.name = getStringCopy(getLoginName());
2165   ldi.author = getStringCopy(getRealName());
2166   ldi.levels = 100;
2167   ldi.first_level = 1;
2168   ldi.sort_priority = LEVELCLASS_USER_START;
2169   ldi.readonly = FALSE;
2170   ldi.graphics_set = getStringCopy(GRAPHICS_SUBDIR);
2171   ldi.sounds_set = getStringCopy(SOUNDS_SUBDIR);
2172   ldi.music_set = getStringCopy(MUSIC_SUBDIR);
2173
2174   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2175                                                  getCookie("LEVELINFO")));
2176
2177   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2178     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2179         i != LEVELINFO_TOKEN_NAME_SORTING &&
2180         i != LEVELINFO_TOKEN_IMPORTED_FROM)
2181       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2182
2183   fclose(file);
2184   free(filename);
2185
2186   SetFilePermissions(filename, PERMS_PRIVATE);
2187 }
2188
2189 char *getSetupValue(int type, void *value)
2190 {
2191   static char value_string[MAX_LINE_LEN];
2192
2193   if (value == NULL)
2194     return NULL;
2195
2196   switch (type)
2197   {
2198     case TYPE_BOOLEAN:
2199       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2200       break;
2201
2202     case TYPE_SWITCH:
2203       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2204       break;
2205
2206     case TYPE_YES_NO:
2207       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2208       break;
2209
2210     case TYPE_KEY:
2211       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2212       break;
2213
2214     case TYPE_KEY_X11:
2215       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2216       break;
2217
2218     case TYPE_INTEGER:
2219       sprintf(value_string, "%d", *(int *)value);
2220       break;
2221
2222     case TYPE_STRING:
2223       strcpy(value_string, *(char **)value);
2224       break;
2225
2226     default:
2227       value_string[0] = '\0';
2228       break;
2229   }
2230
2231   return value_string;
2232 }
2233
2234 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2235 {
2236   int i;
2237   char *line;
2238   static char token_string[MAX_LINE_LEN];
2239   int token_type = token_info[token_nr].type;
2240   void *setup_value = token_info[token_nr].value;
2241   char *token_text = token_info[token_nr].text;
2242   char *value_string = getSetupValue(token_type, setup_value);
2243
2244   /* build complete token string */
2245   sprintf(token_string, "%s%s", prefix, token_text);
2246
2247   /* build setup entry line */
2248   line = getFormattedSetupEntry(token_string, value_string);
2249
2250   if (token_type == TYPE_KEY_X11)
2251   {
2252     Key key = *(Key *)setup_value;
2253     char *keyname = getKeyNameFromKey(key);
2254
2255     /* add comment, if useful */
2256     if (strcmp(keyname, "(undefined)") != 0 &&
2257         strcmp(keyname, "(unknown)") != 0)
2258     {
2259       /* add at least one whitespace */
2260       strcat(line, " ");
2261       for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2262         strcat(line, " ");
2263
2264       strcat(line, "# ");
2265       strcat(line, keyname);
2266     }
2267   }
2268
2269   return line;
2270 }
2271
2272 void LoadLevelSetup_LastSeries()
2273 {
2274   char *filename;
2275   SetupFileHash *level_setup_hash = NULL;
2276
2277   /* always start with reliable default values */
2278   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2279
2280   /* ----------------------------------------------------------------------- */
2281   /* ~/.<program>/levelsetup.conf                                            */
2282   /* ----------------------------------------------------------------------- */
2283
2284   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2285
2286   if ((level_setup_hash = loadSetupFileHash(filename)))
2287   {
2288     char *last_level_series =
2289       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2290
2291     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2292                                                  last_level_series);
2293     if (leveldir_current == NULL)
2294       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2295
2296     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2297
2298     freeSetupFileHash(level_setup_hash);
2299   }
2300   else
2301     Error(ERR_WARN, "using default setup values");
2302
2303   free(filename);
2304 }
2305
2306 void SaveLevelSetup_LastSeries()
2307 {
2308   char *filename;
2309   char *level_subdir = leveldir_current->filename;
2310   FILE *file;
2311
2312   /* ----------------------------------------------------------------------- */
2313   /* ~/.<program>/levelsetup.conf                                            */
2314   /* ----------------------------------------------------------------------- */
2315
2316   InitUserDataDirectory();
2317
2318   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2319
2320   if (!(file = fopen(filename, MODE_WRITE)))
2321   {
2322     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2323     free(filename);
2324     return;
2325   }
2326
2327   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2328                                                  getCookie("LEVELSETUP")));
2329   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2330                                                level_subdir));
2331
2332   fclose(file);
2333   free(filename);
2334
2335   SetFilePermissions(filename, PERMS_PRIVATE);
2336 }
2337
2338 static void checkSeriesInfo()
2339 {
2340   static char *level_directory = NULL;
2341   DIR *dir;
2342   struct dirent *dir_entry;
2343
2344   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2345
2346   level_directory = getPath2((leveldir_current->user_defined ?
2347                               getUserLevelDir(NULL) :
2348                               options.level_directory),
2349                              leveldir_current->fullpath);
2350
2351   if ((dir = opendir(level_directory)) == NULL)
2352   {
2353     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2354     return;
2355   }
2356
2357   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2358   {
2359     if (strlen(dir_entry->d_name) > 4 &&
2360         dir_entry->d_name[3] == '.' &&
2361         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2362     {
2363       char levelnum_str[4];
2364       int levelnum_value;
2365
2366       strncpy(levelnum_str, dir_entry->d_name, 3);
2367       levelnum_str[3] = '\0';
2368
2369       levelnum_value = atoi(levelnum_str);
2370
2371 #if 0
2372       if (levelnum_value < leveldir_current->first_level)
2373       {
2374         Error(ERR_WARN, "additional level %d found", levelnum_value);
2375         leveldir_current->first_level = levelnum_value;
2376       }
2377       else if (levelnum_value > leveldir_current->last_level)
2378       {
2379         Error(ERR_WARN, "additional level %d found", levelnum_value);
2380         leveldir_current->last_level = levelnum_value;
2381       }
2382 #endif
2383     }
2384   }
2385
2386   closedir(dir);
2387 }
2388
2389 void LoadLevelSetup_SeriesInfo()
2390 {
2391   char *filename;
2392   SetupFileHash *level_setup_hash = NULL;
2393   char *level_subdir = leveldir_current->filename;
2394
2395   /* always start with reliable default values */
2396   level_nr = leveldir_current->first_level;
2397
2398   checkSeriesInfo(leveldir_current);
2399
2400   /* ----------------------------------------------------------------------- */
2401   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2402   /* ----------------------------------------------------------------------- */
2403
2404   level_subdir = leveldir_current->filename;
2405
2406   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2407
2408   if ((level_setup_hash = loadSetupFileHash(filename)))
2409   {
2410     char *token_value;
2411
2412     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2413
2414     if (token_value)
2415     {
2416       level_nr = atoi(token_value);
2417
2418       if (level_nr < leveldir_current->first_level)
2419         level_nr = leveldir_current->first_level;
2420       if (level_nr > leveldir_current->last_level)
2421         level_nr = leveldir_current->last_level;
2422     }
2423
2424     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2425
2426     if (token_value)
2427     {
2428       int level_nr = atoi(token_value);
2429
2430       if (level_nr < leveldir_current->first_level)
2431         level_nr = leveldir_current->first_level;
2432       if (level_nr > leveldir_current->last_level + 1)
2433         level_nr = leveldir_current->last_level;
2434
2435       if (leveldir_current->user_defined)
2436         level_nr = leveldir_current->last_level;
2437
2438       leveldir_current->handicap_level = level_nr;
2439     }
2440
2441     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2442
2443     freeSetupFileHash(level_setup_hash);
2444   }
2445   else
2446     Error(ERR_WARN, "using default setup values");
2447
2448   free(filename);
2449 }
2450
2451 void SaveLevelSetup_SeriesInfo()
2452 {
2453   char *filename;
2454   char *level_subdir = leveldir_current->filename;
2455   char *level_nr_str = int2str(level_nr, 0);
2456   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2457   FILE *file;
2458
2459   /* ----------------------------------------------------------------------- */
2460   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2461   /* ----------------------------------------------------------------------- */
2462
2463   InitLevelSetupDirectory(level_subdir);
2464
2465   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2466
2467   if (!(file = fopen(filename, MODE_WRITE)))
2468   {
2469     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2470     free(filename);
2471     return;
2472   }
2473
2474   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2475                                                  getCookie("LEVELSETUP")));
2476   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2477                                                level_nr_str));
2478   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2479                                                handicap_level_str));
2480
2481   fclose(file);
2482   free(filename);
2483
2484   SetFilePermissions(filename, PERMS_PRIVATE);
2485 }