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