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