rnd-20021230-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 int get_string_integer_value(char *s)
1017 {
1018   static char *number_text[][3] =
1019   {
1020     { "0", "zero", "null", },
1021     { "1", "one", "first" },
1022     { "2", "two", "second" },
1023     { "3", "three", "third" },
1024     { "4", "four", "fourth" },
1025     { "5", "five", "fifth" },
1026     { "6", "six", "sixth" },
1027     { "7", "seven", "seventh" },
1028     { "8", "eight", "eighth" },
1029     { "9", "nine", "ninth" },
1030     { "10", "ten", "tenth" },
1031     { "11", "eleven", "eleventh" },
1032     { "12", "twelve", "twelfth" },
1033   };
1034
1035   int i, j;
1036   char *s_lower = getStringToLower(s);
1037   int result = -1;
1038
1039   for (i=0; i<13; i++)
1040     for (j=0; j<3; j++)
1041       if (strcmp(s_lower, number_text[i][j]) == 0)
1042         result = i;
1043
1044   if (result == -1)
1045     result = atoi(s);
1046
1047   free(s_lower);
1048
1049   return result;
1050 }
1051
1052 boolean get_string_boolean_value(char *s)
1053 {
1054   char *s_lower = getStringToLower(s);
1055   boolean result = FALSE;
1056
1057   if (strcmp(s_lower, "true") == 0 ||
1058       strcmp(s_lower, "yes") == 0 ||
1059       strcmp(s_lower, "on") == 0 ||
1060       get_string_integer_value(s) == 1)
1061     result = TRUE;
1062
1063   free(s_lower);
1064
1065   return result;
1066 }
1067
1068 char *getFormattedSetupEntry(char *token, char *value)
1069 {
1070   int i;
1071   static char entry[MAX_LINE_LEN];
1072
1073   /* start with the token and some spaces to format output line */
1074   sprintf(entry, "%s:", token);
1075   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1076     strcat(entry, " ");
1077
1078   /* continue with the token's value */
1079   strcat(entry, value);
1080
1081   return entry;
1082 }
1083
1084 void freeSetupFileList(struct SetupFileList *setup_file_list)
1085 {
1086   if (!setup_file_list)
1087     return;
1088
1089   if (setup_file_list->token)
1090     free(setup_file_list->token);
1091   if (setup_file_list->value)
1092     free(setup_file_list->value);
1093   if (setup_file_list->next)
1094     freeSetupFileList(setup_file_list->next);
1095   free(setup_file_list);
1096 }
1097
1098 static struct SetupFileList *newSetupFileList(char *token, char *value)
1099 {
1100   struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
1101
1102   new->token = checked_malloc(strlen(token) + 1);
1103   strcpy(new->token, token);
1104
1105   new->value = checked_malloc(strlen(value) + 1);
1106   strcpy(new->value, value);
1107
1108   new->next = NULL;
1109
1110   return new;
1111 }
1112
1113 char *getTokenValue(struct SetupFileList *setup_file_list, char *token)
1114 {
1115   if (!setup_file_list)
1116     return NULL;
1117
1118   if (strcmp(setup_file_list->token, token) == 0)
1119     return setup_file_list->value;
1120   else
1121     return getTokenValue(setup_file_list->next, token);
1122 }
1123
1124 static void setTokenValue(struct SetupFileList *setup_file_list,
1125                           char *token, char *value)
1126 {
1127   if (!setup_file_list)
1128     return;
1129
1130   if (strcmp(setup_file_list->token, token) == 0)
1131   {
1132     free(setup_file_list->value);
1133     setup_file_list->value = checked_malloc(strlen(value) + 1);
1134     strcpy(setup_file_list->value, value);
1135   }
1136   else if (setup_file_list->next == NULL)
1137     setup_file_list->next = newSetupFileList(token, value);
1138   else
1139     setTokenValue(setup_file_list->next, token, value);
1140 }
1141
1142 #ifdef DEBUG
1143 static void printSetupFileList(struct SetupFileList *setup_file_list)
1144 {
1145   if (!setup_file_list)
1146     return;
1147
1148   printf("token: '%s'\n", setup_file_list->token);
1149   printf("value: '%s'\n", setup_file_list->value);
1150
1151   printSetupFileList(setup_file_list->next);
1152 }
1153 #endif
1154
1155 struct SetupFileList *loadSetupFileList(char *filename)
1156 {
1157   int line_len;
1158   char line[MAX_LINE_LEN];
1159   char *token, *value, *line_ptr;
1160   struct SetupFileList *setup_file_list = newSetupFileList("", "");
1161   struct SetupFileList *first_valid_list_entry;
1162
1163   FILE *file;
1164
1165   if (!(file = fopen(filename, MODE_READ)))
1166   {
1167     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1168     return NULL;
1169   }
1170
1171   while(!feof(file))
1172   {
1173     /* read next line of input file */
1174     if (!fgets(line, MAX_LINE_LEN, file))
1175       break;
1176
1177     /* cut trailing comment or whitespace from input line */
1178     for (line_ptr = line; *line_ptr; line_ptr++)
1179     {
1180       if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1181       {
1182         *line_ptr = '\0';
1183         break;
1184       }
1185     }
1186
1187     /* cut trailing whitespaces from input line */
1188     for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1189       if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1190         *line_ptr = '\0';
1191
1192     /* ignore empty lines */
1193     if (*line == '\0')
1194       continue;
1195
1196     line_len = strlen(line);
1197
1198     /* cut leading whitespaces from token */
1199     for (token = line; *token; token++)
1200       if (*token != ' ' && *token != '\t')
1201         break;
1202
1203     /* find end of token */
1204     for (line_ptr = token; *line_ptr; line_ptr++)
1205     {
1206       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1207       {
1208         *line_ptr = '\0';
1209         break;
1210       }
1211     }
1212
1213     if (line_ptr < line + line_len)
1214       value = line_ptr + 1;
1215     else
1216       value = "\0";
1217
1218     /* cut leading whitespaces from value */
1219     for (; *value; value++)
1220       if (*value != ' ' && *value != '\t')
1221         break;
1222
1223     if (*token && *value)
1224       setTokenValue(setup_file_list, token, value);
1225   }
1226
1227   fclose(file);
1228
1229   first_valid_list_entry = setup_file_list->next;
1230
1231   /* free empty list header */
1232   setup_file_list->next = NULL;
1233   freeSetupFileList(setup_file_list);
1234
1235   if (first_valid_list_entry == NULL)
1236     Error(ERR_WARN, "configuration file '%s' is empty", filename);
1237
1238   return first_valid_list_entry;
1239 }
1240
1241 void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
1242                                   char *identifier)
1243 {
1244   if (!setup_file_list)
1245     return;
1246
1247   if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
1248   {
1249     if (!checkCookieString(setup_file_list->value, identifier))
1250     {
1251       Error(ERR_WARN, "configuration file has wrong file identifier");
1252       return;
1253     }
1254     else
1255       return;
1256   }
1257
1258   if (setup_file_list->next)
1259     checkSetupFileListIdentifier(setup_file_list->next, identifier);
1260   else
1261   {
1262     Error(ERR_WARN, "configuration file has no file identifier");
1263     return;
1264   }
1265 }
1266
1267
1268 /* ========================================================================= */
1269 /* setup file stuff                                                          */
1270 /* ========================================================================= */
1271
1272 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1273 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1274 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1275
1276 /* level directory info */
1277 #define LEVELINFO_TOKEN_IDENTIFIER      0
1278 #define LEVELINFO_TOKEN_NAME            1
1279 #define LEVELINFO_TOKEN_NAME_SORTING    2
1280 #define LEVELINFO_TOKEN_AUTHOR          3
1281 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1282 #define LEVELINFO_TOKEN_LEVELS          5
1283 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
1284 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
1285 #define LEVELINFO_TOKEN_LEVEL_GROUP     8
1286 #define LEVELINFO_TOKEN_READONLY        9
1287 #define LEVELINFO_TOKEN_GRAPHICS_SET    10
1288 #define LEVELINFO_TOKEN_SOUNDS_SET      11
1289 #define LEVELINFO_TOKEN_MUSIC_SET       12
1290
1291 #define NUM_LEVELINFO_TOKENS            13
1292
1293 static LevelDirTree ldi;
1294
1295 static struct TokenInfo levelinfo_tokens[] =
1296 {
1297   /* level directory info */
1298   { TYPE_STRING,  &ldi.identifier,      "identifier"    },
1299   { TYPE_STRING,  &ldi.name,            "name"          },
1300   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1301   { TYPE_STRING,  &ldi.author,          "author"        },
1302   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1303   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1304   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1305   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1306   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1307   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      },
1308   { TYPE_STRING,  &ldi.graphics_set,    "graphics_set"  },
1309   { TYPE_STRING,  &ldi.sounds_set,      "sounds_set"    },
1310   { TYPE_STRING,  &ldi.music_set,       "music_set"     }
1311 };
1312
1313 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1314 {
1315   ldi->type = type;
1316
1317   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1318                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1319                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1320                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1321                    NULL);
1322
1323   ldi->node_parent = NULL;
1324   ldi->node_group = NULL;
1325   ldi->next = NULL;
1326
1327   ldi->cl_first = -1;
1328   ldi->cl_cursor = -1;
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(ANONYMOUS_NAME);
1337
1338   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1339   ldi->parent_link = FALSE;
1340   ldi->user_defined = FALSE;
1341   ldi->color = 0;
1342   ldi->class_desc = NULL;
1343
1344   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1345   {
1346     ldi->imported_from = NULL;
1347     ldi->graphics_set = NULL;
1348     ldi->sounds_set = NULL;
1349     ldi->music_set = NULL;
1350     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1351     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1352     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1353     ldi->levels = 0;
1354     ldi->first_level = 0;
1355     ldi->last_level = 0;
1356     ldi->level_group = FALSE;
1357     ldi->handicap_level = 0;
1358     ldi->readonly = TRUE;
1359   }
1360 }
1361
1362 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1363 {
1364   if (parent == NULL)
1365   {
1366     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1367
1368     setTreeInfoToDefaults(ldi, TREE_TYPE_GENERIC);
1369     return;
1370   }
1371
1372   /* first copy all values from the parent structure ... */
1373   *ldi = *parent;
1374
1375   /* ... then set all fields to default that cannot be inherited from parent.
1376      This is especially important for all those fields that can be set from
1377      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1378      calls 'free()' for all already set token values which requires that no
1379      other structure's pointer may point to them!
1380   */
1381
1382   ldi->filename = NULL;
1383   ldi->fullpath = NULL;
1384   ldi->basepath = NULL;
1385   ldi->identifier = NULL;
1386   ldi->name = getStringCopy(ANONYMOUS_NAME);
1387   ldi->name_sorting = NULL;
1388   ldi->author = getStringCopy(parent->author);
1389   ldi->imported_from = getStringCopy(parent->imported_from);
1390
1391   ldi->level_group = FALSE;
1392   ldi->parent_link = FALSE;
1393
1394   ldi->node_top = parent->node_top;
1395   ldi->node_parent = parent;
1396   ldi->node_group = NULL;
1397   ldi->next = NULL;
1398 }
1399
1400 void setSetupInfo(struct TokenInfo *token_info,
1401                   int token_nr, char *token_value)
1402 {
1403   int token_type = token_info[token_nr].type;
1404   void *setup_value = token_info[token_nr].value;
1405
1406   if (token_value == NULL)
1407     return;
1408
1409   /* set setup field to corresponding token value */
1410   switch (token_type)
1411   {
1412     case TYPE_BOOLEAN:
1413     case TYPE_SWITCH:
1414       *(boolean *)setup_value = get_string_boolean_value(token_value);
1415       break;
1416
1417     case TYPE_KEY:
1418       *(Key *)setup_value = getKeyFromKeyName(token_value);
1419       break;
1420
1421     case TYPE_KEY_X11:
1422       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1423       break;
1424
1425     case TYPE_INTEGER:
1426       *(int *)setup_value = get_string_integer_value(token_value);
1427       break;
1428
1429     case TYPE_STRING:
1430       if (*(char **)setup_value != NULL)
1431         free(*(char **)setup_value);
1432       *(char **)setup_value = getStringCopy(token_value);
1433       break;
1434
1435     default:
1436       break;
1437   }
1438 }
1439
1440 static int compareTreeInfoEntries(const void *object1, const void *object2)
1441 {
1442   const TreeInfo *entry1 = *((TreeInfo **)object1);
1443   const TreeInfo *entry2 = *((TreeInfo **)object2);
1444   int class_sorting1, class_sorting2;
1445   int compare_result;
1446
1447   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1448   {
1449     class_sorting1 = LEVELSORTING(entry1);
1450     class_sorting2 = LEVELSORTING(entry2);
1451   }
1452   else
1453   {
1454     class_sorting1 = ARTWORKSORTING(entry1);
1455     class_sorting2 = ARTWORKSORTING(entry2);
1456   }
1457
1458   if (entry1->parent_link || entry2->parent_link)
1459     compare_result = (entry1->parent_link ? -1 : +1);
1460   else if (entry1->sort_priority == entry2->sort_priority)
1461   {
1462     char *name1 = getStringToLower(entry1->name_sorting);
1463     char *name2 = getStringToLower(entry2->name_sorting);
1464
1465     compare_result = strcmp(name1, name2);
1466
1467     free(name1);
1468     free(name2);
1469   }
1470   else if (class_sorting1 == class_sorting2)
1471     compare_result = entry1->sort_priority - entry2->sort_priority;
1472   else
1473     compare_result = class_sorting1 - class_sorting2;
1474
1475   return compare_result;
1476 }
1477
1478 static void createParentTreeInfoNode(TreeInfo *node_parent)
1479 {
1480   TreeInfo *ti_new;
1481
1482   if (node_parent == NULL)
1483     return;
1484
1485   ti_new = newTreeInfo();
1486   setTreeInfoToDefaults(ti_new, node_parent->type);
1487
1488   ti_new->node_parent = node_parent;
1489   ti_new->parent_link = TRUE;
1490
1491   ti_new->identifier = getStringCopy(node_parent->identifier);
1492   ti_new->name = ".. (parent directory)";
1493   ti_new->name_sorting = getStringCopy(ti_new->name);
1494
1495   ti_new->filename = "..";
1496   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1497
1498   ti_new->sort_priority = node_parent->sort_priority;
1499   ti_new->class_desc = getLevelClassDescription(ti_new);
1500
1501   pushTreeInfo(&node_parent->node_group, ti_new);
1502 }
1503
1504 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1505 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1506
1507 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1508                                           TreeInfo *node_parent,
1509                                           char *level_directory,
1510                                           char *directory_name)
1511 {
1512   char *directory_path = getPath2(level_directory, directory_name);
1513   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1514   struct SetupFileList *setup_file_list = loadSetupFileList(filename);
1515   LevelDirTree *leveldir_new = NULL;
1516   int i;
1517
1518   if (setup_file_list == NULL)
1519   {
1520     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1521
1522     free(directory_path);
1523     free(filename);
1524
1525     return FALSE;
1526   }
1527
1528   leveldir_new = newTreeInfo();
1529
1530   if (node_parent)
1531     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1532   else
1533     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1534
1535   leveldir_new->filename = getStringCopy(directory_name);
1536
1537   checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1538
1539   /* set all structure fields according to the token/value pairs */
1540   ldi = *leveldir_new;
1541   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1542     setSetupInfo(levelinfo_tokens, i,
1543                  getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1544   *leveldir_new = ldi;
1545
1546   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1547   {
1548     free(leveldir_new->name);
1549     leveldir_new->name = getStringCopy(leveldir_new->filename);
1550   }
1551
1552   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1553
1554   if (leveldir_new->identifier == NULL)
1555     leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1556
1557   if (leveldir_new->name_sorting == NULL)
1558     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1559
1560   if (node_parent == NULL)              /* top level group */
1561   {
1562     leveldir_new->basepath = level_directory;
1563     leveldir_new->fullpath = leveldir_new->filename;
1564   }
1565   else                                  /* sub level group */
1566   {
1567     leveldir_new->basepath = node_parent->basepath;
1568     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1569   }
1570
1571   if (leveldir_new->levels < 1)
1572     leveldir_new->levels = 1;
1573
1574   leveldir_new->last_level =
1575     leveldir_new->first_level + leveldir_new->levels - 1;
1576
1577   leveldir_new->user_defined =
1578     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1579
1580   leveldir_new->color = LEVELCOLOR(leveldir_new);
1581   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1582
1583   leveldir_new->handicap_level =        /* set handicap to default value */
1584     (leveldir_new->user_defined ?
1585      leveldir_new->last_level :
1586      leveldir_new->first_level);
1587
1588   pushTreeInfo(node_first, leveldir_new);
1589
1590   freeSetupFileList(setup_file_list);
1591
1592   if (leveldir_new->level_group)
1593   {
1594     /* create node to link back to current level directory */
1595     createParentTreeInfoNode(leveldir_new);
1596
1597     /* step into sub-directory and look for more level series */
1598     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1599                               leveldir_new, directory_path);
1600   }
1601
1602   free(directory_path);
1603   free(filename);
1604
1605   return TRUE;
1606 }
1607
1608 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1609                                       TreeInfo *node_parent,
1610                                       char *level_directory)
1611 {
1612   DIR *dir;
1613   struct dirent *dir_entry;
1614   boolean valid_entry_found = FALSE;
1615
1616   if ((dir = opendir(level_directory)) == NULL)
1617   {
1618     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1619     return;
1620   }
1621
1622   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1623   {
1624     struct stat file_status;
1625     char *directory_name = dir_entry->d_name;
1626     char *directory_path = getPath2(level_directory, directory_name);
1627
1628     /* skip entries for current and parent directory */
1629     if (strcmp(directory_name, ".")  == 0 ||
1630         strcmp(directory_name, "..") == 0)
1631     {
1632       free(directory_path);
1633       continue;
1634     }
1635
1636     /* find out if directory entry is itself a directory */
1637     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1638         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1639     {
1640       free(directory_path);
1641       continue;
1642     }
1643
1644     free(directory_path);
1645
1646     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
1647         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
1648         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
1649       continue;
1650
1651     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1652                                                     level_directory,
1653                                                     directory_name);
1654   }
1655
1656   closedir(dir);
1657
1658   if (!valid_entry_found)
1659   {
1660     /* check if this directory directly contains a file "levelinfo.conf" */
1661     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1662                                                     level_directory, ".");
1663   }
1664
1665   if (!valid_entry_found)
1666     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1667           level_directory);
1668 }
1669
1670 void LoadLevelInfo()
1671 {
1672   InitUserLevelDirectory(getLoginName());
1673
1674   DrawInitText("Loading level series:", 120, FC_GREEN);
1675
1676   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1677   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1678
1679   /* before sorting, the first entries will be from the user directory */
1680   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1681
1682   if (leveldir_first == NULL)
1683     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1684
1685   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1686
1687 #if 0
1688   dumpTreeInfo(leveldir_first, 0);
1689 #endif
1690 }
1691
1692 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1693                                               TreeInfo *node_parent,
1694                                               char *base_directory,
1695                                               char *directory_name, int type)
1696 {
1697   char *directory_path = getPath2(base_directory, directory_name);
1698   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
1699   struct SetupFileList *setup_file_list = NULL;
1700   TreeInfo *artwork_new = NULL;
1701   int i;
1702
1703   if (access(filename, F_OK) == 0)              /* file exists */
1704     setup_file_list = loadSetupFileList(filename);
1705
1706   if (setup_file_list == NULL)  /* no config file -- look for artwork files */
1707   {
1708     DIR *dir;
1709     struct dirent *dir_entry;
1710     boolean valid_file_found = FALSE;
1711
1712     if ((dir = opendir(directory_path)) != NULL)
1713     {
1714       while ((dir_entry = readdir(dir)) != NULL)
1715       {
1716         char *entry_name = dir_entry->d_name;
1717
1718         if (FileIsArtworkType(entry_name, type))
1719         {
1720           valid_file_found = TRUE;
1721           break;
1722         }
1723       }
1724
1725       closedir(dir);
1726     }
1727
1728     if (!valid_file_found)
1729     {
1730       if (strcmp(directory_name, ".") != 0)
1731         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
1732
1733       free(directory_path);
1734       free(filename);
1735
1736       return FALSE;
1737     }
1738   }
1739
1740   artwork_new = newTreeInfo();
1741
1742   if (node_parent)
1743     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1744   else
1745     setTreeInfoToDefaults(artwork_new, type);
1746
1747   artwork_new->filename = getStringCopy(directory_name);
1748
1749   if (setup_file_list)  /* (before defining ".color" and ".class_desc") */
1750   {
1751 #if 0
1752     checkSetupFileListIdentifier(setup_file_list, getCookie("..."));
1753 #endif
1754
1755     /* set all structure fields according to the token/value pairs */
1756     ldi = *artwork_new;
1757     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1758       setSetupInfo(levelinfo_tokens, i,
1759                    getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1760     *artwork_new = ldi;
1761
1762     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
1763     {
1764       free(artwork_new->name);
1765       artwork_new->name = getStringCopy(artwork_new->filename);
1766     }
1767
1768 #if 0
1769     DrawInitText(artwork_new->name, 150, FC_YELLOW);
1770 #endif
1771
1772     if (artwork_new->identifier == NULL)
1773       artwork_new->identifier = getStringCopy(artwork_new->filename);
1774
1775     if (artwork_new->name_sorting == NULL)
1776       artwork_new->name_sorting = getStringCopy(artwork_new->name);
1777   }
1778
1779   if (node_parent == NULL)              /* top level group */
1780   {
1781     artwork_new->basepath = getStringCopy(base_directory);
1782     artwork_new->fullpath = getStringCopy(artwork_new->filename);
1783   }
1784   else                                  /* sub level group */
1785   {
1786     artwork_new->basepath = getStringCopy(node_parent->basepath);
1787     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1788   }
1789
1790   artwork_new->user_defined =
1791     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
1792
1793   /* (may use ".sort_priority" from "setup_file_list" above) */
1794   artwork_new->color = ARTWORKCOLOR(artwork_new);
1795   artwork_new->class_desc = getLevelClassDescription(artwork_new);
1796
1797   if (setup_file_list == NULL)  /* (after determining ".user_defined") */
1798   {
1799     if (artwork_new->name != NULL)
1800       free(artwork_new->name);
1801
1802     if (strcmp(artwork_new->filename, ".") == 0)
1803     {
1804       if (artwork_new->user_defined)
1805       {
1806         artwork_new->identifier = getStringCopy("private");
1807         artwork_new->sort_priority = ARTWORKCLASS_USER;
1808       }
1809       else
1810       {
1811         artwork_new->identifier = getStringCopy("classic");
1812         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
1813       }
1814
1815       /* set to new values after changing ".sort_priority" */
1816       artwork_new->color = ARTWORKCOLOR(artwork_new);
1817       artwork_new->class_desc = getLevelClassDescription(artwork_new);
1818     }
1819     else
1820     {
1821       artwork_new->identifier = getStringCopy(artwork_new->filename);
1822     }
1823
1824     artwork_new->name = getStringCopy(artwork_new->identifier);
1825     artwork_new->name_sorting = getStringCopy(artwork_new->name);
1826   }
1827
1828   DrawInitText(artwork_new->name, 150, FC_YELLOW);
1829
1830   pushTreeInfo(node_first, artwork_new);
1831
1832   freeSetupFileList(setup_file_list);
1833
1834   free(directory_path);
1835   free(filename);
1836
1837   return TRUE;
1838 }
1839
1840 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
1841                                           TreeInfo *node_parent,
1842                                           char *base_directory, int type)
1843 {
1844   DIR *dir;
1845   struct dirent *dir_entry;
1846   boolean valid_entry_found = FALSE;
1847
1848   if ((dir = opendir(base_directory)) == NULL)
1849   {
1850     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
1851       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
1852     return;
1853   }
1854
1855   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1856   {
1857     struct stat file_status;
1858     char *directory_name = dir_entry->d_name;
1859     char *directory_path = getPath2(base_directory, directory_name);
1860
1861     /* skip entries for current and parent directory */
1862     if (strcmp(directory_name, ".")  == 0 ||
1863         strcmp(directory_name, "..") == 0)
1864     {
1865       free(directory_path);
1866       continue;
1867     }
1868
1869     /* find out if directory entry is itself a directory */
1870     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1871         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1872     {
1873       free(directory_path);
1874       continue;
1875     }
1876
1877     free(directory_path);
1878
1879     /* check if this directory contains artwork with or without config file */
1880     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1881                                                         base_directory,
1882                                                         directory_name, type);
1883   }
1884
1885   closedir(dir);
1886
1887   /* check if this directory directly contains artwork itself */
1888   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1889                                                       base_directory, ".",
1890                                                       type);
1891   if (!valid_entry_found)
1892     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
1893           base_directory);
1894 }
1895
1896 static TreeInfo *getDummyArtworkInfo(int type)
1897 {
1898   /* this is only needed when there is completely no artwork available */
1899   TreeInfo *artwork_new = newTreeInfo();
1900
1901   setTreeInfoToDefaults(artwork_new, type);
1902
1903   artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
1904   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
1905   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
1906
1907   if (artwork_new->name != NULL)
1908     free(artwork_new->name);
1909
1910   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
1911   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
1912   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
1913
1914   return artwork_new;
1915 }
1916
1917 void LoadArtworkInfo()
1918 {
1919   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
1920
1921   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1922                                 options.graphics_directory,
1923                                 TREE_TYPE_GRAPHICS_DIR);
1924   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1925                                 getUserGraphicsDir(),
1926                                 TREE_TYPE_GRAPHICS_DIR);
1927
1928   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1929                                 options.sounds_directory,
1930                                 TREE_TYPE_SOUNDS_DIR);
1931   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1932                                 getUserSoundsDir(),
1933                                 TREE_TYPE_SOUNDS_DIR);
1934
1935   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1936                                 options.music_directory,
1937                                 TREE_TYPE_MUSIC_DIR);
1938   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1939                                 getUserMusicDir(),
1940                                 TREE_TYPE_MUSIC_DIR);
1941
1942   if (artwork.gfx_first == NULL)
1943     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
1944   if (artwork.snd_first == NULL)
1945     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
1946   if (artwork.mus_first == NULL)
1947     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
1948
1949   /* before sorting, the first entries will be from the user directory */
1950   artwork.gfx_current =
1951     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
1952   if (artwork.gfx_current == NULL)
1953     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
1954
1955   artwork.snd_current =
1956     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
1957   if (artwork.snd_current == NULL)
1958     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
1959
1960   artwork.mus_current =
1961     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
1962   if (artwork.mus_current == NULL)
1963     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
1964
1965   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
1966   artwork.snd_current_identifier = artwork.snd_current->identifier;
1967   artwork.mus_current_identifier = artwork.mus_current->identifier;
1968
1969 #if 0
1970   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
1971   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
1972   printf("music set == %s\n\n", artwork.mus_current_identifier);
1973 #endif
1974
1975   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
1976   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
1977   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
1978
1979 #if 0
1980   dumpTreeInfo(artwork.gfx_first, 0);
1981   dumpTreeInfo(artwork.snd_first, 0);
1982   dumpTreeInfo(artwork.mus_first, 0);
1983 #endif
1984 }
1985
1986 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
1987                                   LevelDirTree *level_node)
1988 {
1989   /* recursively check all level directories for artwork sub-directories */
1990
1991   while (level_node)
1992   {
1993     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
1994                           ARTWORK_DIRECTORY((*artwork_node)->type));
1995
1996 #if 0
1997     if (!level_node->parent_link)
1998       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
1999              level_node->filename, level_node->name);
2000 #endif
2001
2002     if (!level_node->parent_link)
2003     {
2004       TreeInfo *topnode_last = *artwork_node;
2005
2006       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2007                                     (*artwork_node)->type);
2008
2009       if (topnode_last != *artwork_node)
2010       {
2011         free((*artwork_node)->identifier);
2012         free((*artwork_node)->name);
2013         free((*artwork_node)->name_sorting);
2014
2015         (*artwork_node)->identifier   = getStringCopy(level_node->filename);
2016         (*artwork_node)->name         = getStringCopy(level_node->name);
2017         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2018
2019         (*artwork_node)->sort_priority = level_node->sort_priority;
2020         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2021       }
2022     }
2023
2024     free(path);
2025
2026     if (level_node->node_group != NULL)
2027       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2028
2029     level_node = level_node->next;
2030   }
2031 }
2032
2033 void LoadLevelArtworkInfo()
2034 {
2035   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2036
2037   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2038   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2039   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2040
2041   /* needed for reloading level artwork not known at ealier stage */
2042   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2043   {
2044     artwork.gfx_current =
2045       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2046     if (artwork.gfx_current == NULL)
2047       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2048   }
2049
2050   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2051   {
2052     artwork.snd_current =
2053       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2054     if (artwork.snd_current == NULL)
2055       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2056   }
2057
2058   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2059   {
2060     artwork.mus_current =
2061       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2062     if (artwork.mus_current == NULL)
2063       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2064   }
2065
2066   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2067   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2068   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2069
2070 #if 0
2071   dumpTreeInfo(artwork.gfx_first, 0);
2072   dumpTreeInfo(artwork.snd_first, 0);
2073   dumpTreeInfo(artwork.mus_first, 0);
2074 #endif
2075 }
2076
2077 static void SaveUserLevelInfo()
2078 {
2079   char *filename;
2080   FILE *file;
2081   int i;
2082
2083   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2084
2085   if (!(file = fopen(filename, MODE_WRITE)))
2086   {
2087     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2088     free(filename);
2089     return;
2090   }
2091
2092   /* always start with reliable default values */
2093   setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
2094
2095   ldi.name = getStringCopy(getLoginName());
2096   ldi.author = getStringCopy(getRealName());
2097   ldi.levels = 100;
2098   ldi.first_level = 1;
2099   ldi.sort_priority = LEVELCLASS_USER_START;
2100   ldi.readonly = FALSE;
2101   ldi.graphics_set = getStringCopy(GRAPHICS_SUBDIR);
2102   ldi.sounds_set = getStringCopy(SOUNDS_SUBDIR);
2103   ldi.music_set = getStringCopy(MUSIC_SUBDIR);
2104
2105   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2106                                                  getCookie("LEVELINFO")));
2107
2108   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2109     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2110         i != LEVELINFO_TOKEN_NAME_SORTING &&
2111         i != LEVELINFO_TOKEN_IMPORTED_FROM)
2112       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2113
2114   fclose(file);
2115   free(filename);
2116
2117   SetFilePermissions(filename, PERMS_PRIVATE);
2118 }
2119
2120 char *getSetupValue(int type, void *value)
2121 {
2122   static char value_string[MAX_LINE_LEN];
2123
2124   if (value == NULL)
2125     return NULL;
2126
2127   switch (type)
2128   {
2129     case TYPE_BOOLEAN:
2130       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2131       break;
2132
2133     case TYPE_SWITCH:
2134       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2135       break;
2136
2137     case TYPE_YES_NO:
2138       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2139       break;
2140
2141     case TYPE_KEY:
2142       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2143       break;
2144
2145     case TYPE_KEY_X11:
2146       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2147       break;
2148
2149     case TYPE_INTEGER:
2150       sprintf(value_string, "%d", *(int *)value);
2151       break;
2152
2153     case TYPE_STRING:
2154       strcpy(value_string, *(char **)value);
2155       break;
2156
2157     default:
2158       value_string[0] = '\0';
2159       break;
2160   }
2161
2162   return value_string;
2163 }
2164
2165 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2166 {
2167   int i;
2168   char *line;
2169   static char token_string[MAX_LINE_LEN];
2170   int token_type = token_info[token_nr].type;
2171   void *setup_value = token_info[token_nr].value;
2172   char *token_text = token_info[token_nr].text;
2173   char *value_string = getSetupValue(token_type, setup_value);
2174
2175   /* build complete token string */
2176   sprintf(token_string, "%s%s", prefix, token_text);
2177
2178   /* build setup entry line */
2179   line = getFormattedSetupEntry(token_string, value_string);
2180
2181   if (token_type == TYPE_KEY_X11)
2182   {
2183     Key key = *(Key *)setup_value;
2184     char *keyname = getKeyNameFromKey(key);
2185
2186     /* add comment, if useful */
2187     if (strcmp(keyname, "(undefined)") != 0 &&
2188         strcmp(keyname, "(unknown)") != 0)
2189     {
2190       /* add at least one whitespace */
2191       strcat(line, " ");
2192       for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2193         strcat(line, " ");
2194
2195       strcat(line, "# ");
2196       strcat(line, keyname);
2197     }
2198   }
2199
2200   return line;
2201 }
2202
2203 void LoadLevelSetup_LastSeries()
2204 {
2205   char *filename;
2206   struct SetupFileList *level_setup_list = NULL;
2207
2208   /* always start with reliable default values */
2209   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2210
2211   /* ----------------------------------------------------------------------- */
2212   /* ~/.<program>/levelsetup.conf                                            */
2213   /* ----------------------------------------------------------------------- */
2214
2215   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2216
2217   if ((level_setup_list = loadSetupFileList(filename)))
2218   {
2219     char *last_level_series =
2220       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
2221
2222     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2223                                                  last_level_series);
2224     if (leveldir_current == NULL)
2225       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2226
2227     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
2228
2229     freeSetupFileList(level_setup_list);
2230   }
2231   else
2232     Error(ERR_WARN, "using default setup values");
2233
2234   free(filename);
2235 }
2236
2237 void SaveLevelSetup_LastSeries()
2238 {
2239   char *filename;
2240   char *level_subdir = leveldir_current->filename;
2241   FILE *file;
2242
2243   /* ----------------------------------------------------------------------- */
2244   /* ~/.<program>/levelsetup.conf                                            */
2245   /* ----------------------------------------------------------------------- */
2246
2247   InitUserDataDirectory();
2248
2249   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2250
2251   if (!(file = fopen(filename, MODE_WRITE)))
2252   {
2253     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2254     free(filename);
2255     return;
2256   }
2257
2258   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2259                                                  getCookie("LEVELSETUP")));
2260   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2261                                                level_subdir));
2262
2263   fclose(file);
2264   free(filename);
2265
2266   SetFilePermissions(filename, PERMS_PRIVATE);
2267 }
2268
2269 static void checkSeriesInfo()
2270 {
2271   static char *level_directory = NULL;
2272   DIR *dir;
2273   struct dirent *dir_entry;
2274
2275   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2276
2277   level_directory = getPath2((leveldir_current->user_defined ?
2278                               getUserLevelDir(NULL) :
2279                               options.level_directory),
2280                              leveldir_current->fullpath);
2281
2282   if ((dir = opendir(level_directory)) == NULL)
2283   {
2284     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2285     return;
2286   }
2287
2288   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2289   {
2290     if (strlen(dir_entry->d_name) > 4 &&
2291         dir_entry->d_name[3] == '.' &&
2292         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2293     {
2294       char levelnum_str[4];
2295       int levelnum_value;
2296
2297       strncpy(levelnum_str, dir_entry->d_name, 3);
2298       levelnum_str[3] = '\0';
2299
2300       levelnum_value = atoi(levelnum_str);
2301
2302       if (levelnum_value < leveldir_current->first_level)
2303       {
2304         Error(ERR_WARN, "additional level %d found", levelnum_value);
2305         leveldir_current->first_level = levelnum_value;
2306       }
2307       else if (levelnum_value > leveldir_current->last_level)
2308       {
2309         Error(ERR_WARN, "additional level %d found", levelnum_value);
2310         leveldir_current->last_level = levelnum_value;
2311       }
2312     }
2313   }
2314
2315   closedir(dir);
2316 }
2317
2318 void LoadLevelSetup_SeriesInfo()
2319 {
2320   char *filename;
2321   struct SetupFileList *level_setup_list = NULL;
2322   char *level_subdir = leveldir_current->filename;
2323
2324   /* always start with reliable default values */
2325   level_nr = leveldir_current->first_level;
2326
2327   checkSeriesInfo(leveldir_current);
2328
2329   /* ----------------------------------------------------------------------- */
2330   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2331   /* ----------------------------------------------------------------------- */
2332
2333   level_subdir = leveldir_current->filename;
2334
2335   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2336
2337   if ((level_setup_list = loadSetupFileList(filename)))
2338   {
2339     char *token_value;
2340
2341     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
2342
2343     if (token_value)
2344     {
2345       level_nr = atoi(token_value);
2346
2347       if (level_nr < leveldir_current->first_level)
2348         level_nr = leveldir_current->first_level;
2349       if (level_nr > leveldir_current->last_level)
2350         level_nr = leveldir_current->last_level;
2351     }
2352
2353     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
2354
2355     if (token_value)
2356     {
2357       int level_nr = atoi(token_value);
2358
2359       if (level_nr < leveldir_current->first_level)
2360         level_nr = leveldir_current->first_level;
2361       if (level_nr > leveldir_current->last_level + 1)
2362         level_nr = leveldir_current->last_level;
2363
2364       if (leveldir_current->user_defined)
2365         level_nr = leveldir_current->last_level;
2366
2367       leveldir_current->handicap_level = level_nr;
2368     }
2369
2370     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
2371
2372     freeSetupFileList(level_setup_list);
2373   }
2374   else
2375     Error(ERR_WARN, "using default setup values");
2376
2377   free(filename);
2378 }
2379
2380 void SaveLevelSetup_SeriesInfo()
2381 {
2382   char *filename;
2383   char *level_subdir = leveldir_current->filename;
2384   char *level_nr_str = int2str(level_nr, 0);
2385   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2386   FILE *file;
2387
2388   /* ----------------------------------------------------------------------- */
2389   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2390   /* ----------------------------------------------------------------------- */
2391
2392   InitLevelSetupDirectory(level_subdir);
2393
2394   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2395
2396   if (!(file = fopen(filename, MODE_WRITE)))
2397   {
2398     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2399     free(filename);
2400     return;
2401   }
2402
2403   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2404                                                  getCookie("LEVELSETUP")));
2405   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2406                                                level_nr_str));
2407   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2408                                                handicap_level_str));
2409
2410   fclose(file);
2411   free(filename);
2412
2413   SetFilePermissions(filename, PERMS_PRIVATE);
2414 }