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