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