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