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