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