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