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