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