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