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