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