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