rnd-20020406-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("gfx_classic"), 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 *getFirstValidLevelSeries(TreeInfo *node)
503 {
504   if (node == NULL)
505   {
506     if (leveldir_first)         /* start with first level directory entry */
507       return getFirstValidLevelSeries(leveldir_first);
508     else
509       return NULL;
510   }
511   else if (node->node_group)    /* enter level group (step down into tree) */
512     return getFirstValidLevelSeries(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 getFirstValidLevelSeries(node->next);
517     else                        /* leave empty level group and go on */
518       return getFirstValidLevelSeries(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   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1482
1483   if (leveldir_first == NULL)
1484     Error(ERR_EXIT, "cannot find any valid level series in any directory");
1485
1486   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1487
1488 #if 0
1489   dumpTreeInfo(leveldir_first, 0);
1490 #endif
1491 }
1492
1493 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1494                                               TreeInfo *node_parent,
1495                                               char *base_directory,
1496                                               char *directory_name, int type)
1497 {
1498   char *directory_path = getPath2(base_directory, directory_name);
1499   char *filename =
1500     getPath2(directory_path,
1501              (type == TREE_TYPE_GRAPHICS_DIR ? GRAPHICSINFO_FILENAME :
1502               type == TREE_TYPE_SOUNDS_DIR ? SOUNDSINFO_FILENAME :
1503               type == TREE_TYPE_MUSIC_DIR ? MUSICINFO_FILENAME : ""));
1504   struct SetupFileList *setup_file_list = NULL;
1505   TreeInfo *artwork_new = NULL;
1506   char *check_dir = NULL;
1507   int i;
1508
1509   if (access(getUserLevelDir(filename), F_OK) == 0)     /* file exists */
1510     loadSetupFileList(filename);
1511
1512   if (setup_file_list == NULL)  /* no config file -- look for artwork files */
1513   {
1514     DIR *dir;
1515     struct dirent *dir_entry;
1516     boolean valid_file_found = FALSE;
1517
1518     if ((dir = opendir(base_directory)) != NULL)
1519     {
1520       while ((dir_entry = readdir(dir)) != NULL)
1521       {
1522         char *entry_name = dir_entry->d_name;
1523
1524         if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(entry_name)) ||
1525             (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(entry_name)) ||
1526             (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(entry_name)))
1527         {
1528           valid_file_found = TRUE;
1529           break;
1530         }
1531       }
1532
1533       closedir(dir);
1534     }
1535
1536     if (!valid_file_found)
1537     {
1538       Error(ERR_WARN, "ignoring artwork directory '%s'", base_directory);
1539
1540       free(directory_path);
1541       free(filename);
1542
1543       return FALSE;
1544     }
1545   }
1546
1547   artwork_new = newTreeInfo();
1548
1549   if (node_parent)
1550     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1551   else
1552     setTreeInfoToDefaults(artwork_new, type);
1553
1554   artwork_new->filename = getStringCopy(directory_name);
1555
1556   if (setup_file_list)  /* (before defining ".color" and ".class_desc") */
1557   {
1558 #if 0
1559     checkSetupFileListIdentifier(setup_file_list, getCookie("..."));
1560 #endif
1561
1562     /* set all structure fields according to the token/value pairs */
1563     ldi = *artwork_new;
1564     for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1565       setSetupInfo(levelinfo_tokens, i,
1566                    getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1567     *artwork_new = ldi;
1568
1569     DrawInitText(artwork_new->name, 150, FC_YELLOW);
1570
1571     if (artwork_new->name_short == NULL)
1572       artwork_new->name_short = getStringCopy(artwork_new->name);
1573
1574     if (artwork_new->name_sorting == NULL)
1575       artwork_new->name_sorting = getStringCopy(artwork_new->name);
1576   }
1577
1578   if (node_parent == NULL)              /* top level group */
1579   {
1580     artwork_new->basepath = base_directory;
1581     artwork_new->fullpath = artwork_new->filename;
1582   }
1583   else                                  /* sub level group */
1584   {
1585     artwork_new->basepath = node_parent->basepath;
1586     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1587   }
1588
1589   check_dir = (type == TREE_TYPE_GRAPHICS_DIR ? options.graphics_directory :
1590                type == TREE_TYPE_SOUNDS_DIR ? options.sounds_directory :
1591                type == TREE_TYPE_MUSIC_DIR ? options.music_directory : "");
1592   artwork_new->user_defined =
1593     (artwork_new->basepath == check_dir ? FALSE : TRUE);
1594
1595   /* (may use ".sort_priority" from "setup_file_list" above) */
1596   artwork_new->color = LEVELCOLOR(artwork_new);
1597   artwork_new->class_desc = getLevelClassDescription(artwork_new);
1598
1599   if (setup_file_list == NULL)  /* (after determining ".user_defined") */
1600   {
1601     if (artwork_new->name != NULL)
1602       free(artwork_new->name);
1603
1604     if (strcmp(artwork_new->filename, ".") == 0)
1605     {
1606       if (artwork_new->user_defined)
1607         artwork_new->name = getStringCopy("private");
1608       else
1609         artwork_new->name = getStringCopy("default");
1610     }
1611     else
1612       artwork_new->name = getStringCopy(artwork_new->filename);
1613
1614     artwork_new->name_short = getStringCopy(artwork_new->name);
1615     artwork_new->name_sorting = getStringCopy(artwork_new->name);
1616   }
1617
1618   pushTreeInfo(node_first, artwork_new);
1619
1620   freeSetupFileList(setup_file_list);
1621
1622   free(directory_path);
1623   free(filename);
1624
1625   return TRUE;
1626 }
1627
1628 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
1629                                           TreeInfo *node_parent,
1630                                           char *base_directory, int type)
1631 {
1632   DIR *dir;
1633   struct dirent *dir_entry;
1634   boolean valid_entry_found = FALSE;
1635
1636   if ((dir = opendir(base_directory)) == NULL)
1637   {
1638     if ((type == TREE_TYPE_GRAPHICS_DIR &&
1639          base_directory == options.graphics_directory) ||
1640         (type == TREE_TYPE_SOUNDS_DIR &&
1641          base_directory == options.sounds_directory) ||
1642         (type == TREE_TYPE_MUSIC_DIR &&
1643          base_directory == options.music_directory))
1644       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
1645     return;
1646   }
1647
1648   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
1649   {
1650     struct stat file_status;
1651     char *directory_name = dir_entry->d_name;
1652     char *directory_path = getPath2(base_directory, directory_name);
1653
1654     /* skip entries for current and parent directory */
1655     if (strcmp(directory_name, ".")  == 0 ||
1656         strcmp(directory_name, "..") == 0)
1657     {
1658       free(directory_path);
1659       continue;
1660     }
1661
1662     /* find out if directory entry is itself a directory */
1663     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
1664         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
1665     {
1666       free(directory_path);
1667       continue;
1668     }
1669
1670     free(directory_path);
1671
1672     /* check if this directory contains artwork with or without config file */
1673     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1674                                                         base_directory,
1675                                                         directory_name, type);
1676   }
1677
1678   closedir(dir);
1679
1680   /* check if this directory directly contains artwork itself */
1681   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1682                                                       base_directory, ".",
1683                                                       type);
1684   if (!valid_entry_found)
1685     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
1686           base_directory);
1687 }
1688
1689 void LoadArtworkInfo()
1690 {
1691   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
1692
1693   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1694                                 options.graphics_directory,
1695                                 TREE_TYPE_GRAPHICS_DIR);
1696   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1697                                 getUserGraphicsDir(),
1698                                 TREE_TYPE_GRAPHICS_DIR);
1699
1700   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1701                                 options.sounds_directory,
1702                                 TREE_TYPE_SOUNDS_DIR);
1703   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
1704                                 getUserSoundsDir(),
1705                                 TREE_TYPE_SOUNDS_DIR);
1706
1707   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1708                                 options.music_directory,
1709                                 TREE_TYPE_MUSIC_DIR);
1710   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
1711                                 getUserMusicDir(),
1712                                 TREE_TYPE_MUSIC_DIR);
1713
1714   /* before sorting, the first entries will be from the user directory */
1715   artwork.gfx_current = artwork.gfx_first;
1716   artwork.snd_current = artwork.snd_first;
1717   artwork.mus_current = artwork.mus_first;
1718
1719   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
1720   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
1721   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
1722
1723 #if 0
1724   dumpTreeInfo(artwork.gfx_first, 0);
1725   dumpTreeInfo(artwork.snd_first, 0);
1726   dumpTreeInfo(artwork.mus_first, 0);
1727 #endif
1728 }
1729
1730 static void SaveUserLevelInfo()
1731 {
1732   char *filename;
1733   FILE *file;
1734   int i;
1735
1736   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1737
1738   if (!(file = fopen(filename, MODE_WRITE)))
1739   {
1740     Error(ERR_WARN, "cannot write level info file '%s'", filename);
1741     free(filename);
1742     return;
1743   }
1744
1745   /* always start with reliable default values */
1746   setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
1747
1748   ldi.name = getLoginName();
1749   ldi.author = getRealName();
1750   ldi.levels = 100;
1751   ldi.first_level = 1;
1752   ldi.sort_priority = LEVELCLASS_USER_START;
1753   ldi.readonly = FALSE;
1754
1755   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1756                                                  getCookie("LEVELINFO")));
1757
1758   for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1759     if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1760         i != LEVELINFO_TOKEN_NAME_SORTING &&
1761         i != LEVELINFO_TOKEN_IMPORTED_FROM)
1762       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1763
1764   fclose(file);
1765   free(filename);
1766
1767   SetFilePermissions(filename, PERMS_PRIVATE);
1768 }
1769
1770 char *getSetupValue(int type, void *value)
1771 {
1772   static char value_string[MAX_LINE_LEN];
1773
1774   switch (type)
1775   {
1776     case TYPE_BOOLEAN:
1777       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
1778       break;
1779
1780     case TYPE_SWITCH:
1781       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
1782       break;
1783
1784     case TYPE_YES_NO:
1785       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
1786       break;
1787
1788     case TYPE_KEY:
1789       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
1790       break;
1791
1792     case TYPE_KEY_X11:
1793       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
1794       break;
1795
1796     case TYPE_INTEGER:
1797       sprintf(value_string, "%d", *(int *)value);
1798       break;
1799
1800     case TYPE_STRING:
1801       strcpy(value_string, *(char **)value);
1802       break;
1803
1804     default:
1805       value_string[0] = '\0';
1806       break;
1807   }
1808
1809   return value_string;
1810 }
1811
1812 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1813 {
1814   int i;
1815   static char entry[MAX_LINE_LEN];
1816   int token_type = token_info[token_nr].type;
1817   void *setup_value = token_info[token_nr].value;
1818   char *token_text = token_info[token_nr].text;
1819   char *value_string = getSetupValue(token_type, setup_value);
1820
1821   /* start with the prefix, token and some spaces to format output line */
1822   sprintf(entry, "%s%s:", prefix, token_text);
1823   for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1824     strcat(entry, " ");
1825
1826   /* continue with the token's value (which can have different types) */
1827   strcat(entry, value_string);
1828
1829   if (token_type == TYPE_KEY_X11)
1830   {
1831     Key key = *(Key *)setup_value;
1832     char *keyname = getKeyNameFromKey(key);
1833
1834     /* add comment, if useful */
1835     if (strcmp(keyname, "(undefined)") != 0 &&
1836         strcmp(keyname, "(unknown)") != 0)
1837     {
1838       for (i=strlen(entry); i<50; i++)
1839         strcat(entry, " ");
1840
1841       strcat(entry, "# ");
1842       strcat(entry, keyname);
1843     }
1844   }
1845
1846   return entry;
1847 }
1848
1849 void LoadLevelSetup_LastSeries()
1850 {
1851   char *filename;
1852   struct SetupFileList *level_setup_list = NULL;
1853
1854   /* always start with reliable default values */
1855   leveldir_current = getFirstValidLevelSeries(leveldir_first);
1856
1857   /* ----------------------------------------------------------------------- */
1858   /* ~/.<program>/levelsetup.conf                                            */
1859   /* ----------------------------------------------------------------------- */
1860
1861   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1862
1863   if ((level_setup_list = loadSetupFileList(filename)))
1864   {
1865     char *last_level_series =
1866       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1867
1868     leveldir_current = getTreeInfoFromFilename(leveldir_first,
1869                                                last_level_series);
1870     if (leveldir_current == NULL)
1871       leveldir_current = leveldir_first;
1872
1873     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1874
1875     freeSetupFileList(level_setup_list);
1876   }
1877   else
1878     Error(ERR_WARN, "using default setup values");
1879
1880   free(filename);
1881 }
1882
1883 void SaveLevelSetup_LastSeries()
1884 {
1885   char *filename;
1886   char *level_subdir = leveldir_current->filename;
1887   FILE *file;
1888
1889   /* ----------------------------------------------------------------------- */
1890   /* ~/.<program>/levelsetup.conf                                            */
1891   /* ----------------------------------------------------------------------- */
1892
1893   InitUserDataDirectory();
1894
1895   filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1896
1897   if (!(file = fopen(filename, MODE_WRITE)))
1898   {
1899     Error(ERR_WARN, "cannot write setup file '%s'", filename);
1900     free(filename);
1901     return;
1902   }
1903
1904   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1905                                                  getCookie("LEVELSETUP")));
1906   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1907                                                level_subdir));
1908
1909   fclose(file);
1910   free(filename);
1911
1912   SetFilePermissions(filename, PERMS_PRIVATE);
1913 }
1914
1915 static void checkSeriesInfo()
1916 {
1917   static char *level_directory = NULL;
1918   DIR *dir;
1919   struct dirent *dir_entry;
1920
1921   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1922
1923   level_directory = getPath2((leveldir_current->user_defined ?
1924                               getUserLevelDir(NULL) :
1925                               options.level_directory),
1926                              leveldir_current->fullpath);
1927
1928   if ((dir = opendir(level_directory)) == NULL)
1929   {
1930     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1931     return;
1932   }
1933
1934   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
1935   {
1936     if (strlen(dir_entry->d_name) > 4 &&
1937         dir_entry->d_name[3] == '.' &&
1938         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1939     {
1940       char levelnum_str[4];
1941       int levelnum_value;
1942
1943       strncpy(levelnum_str, dir_entry->d_name, 3);
1944       levelnum_str[3] = '\0';
1945
1946       levelnum_value = atoi(levelnum_str);
1947
1948       if (levelnum_value < leveldir_current->first_level)
1949       {
1950         Error(ERR_WARN, "additional level %d found", levelnum_value);
1951         leveldir_current->first_level = levelnum_value;
1952       }
1953       else if (levelnum_value > leveldir_current->last_level)
1954       {
1955         Error(ERR_WARN, "additional level %d found", levelnum_value);
1956         leveldir_current->last_level = levelnum_value;
1957       }
1958     }
1959   }
1960
1961   closedir(dir);
1962 }
1963
1964 void LoadLevelSetup_SeriesInfo()
1965 {
1966   char *filename;
1967   struct SetupFileList *level_setup_list = NULL;
1968   char *level_subdir = leveldir_current->filename;
1969
1970   /* always start with reliable default values */
1971   level_nr = leveldir_current->first_level;
1972
1973   checkSeriesInfo(leveldir_current);
1974
1975   /* ----------------------------------------------------------------------- */
1976   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
1977   /* ----------------------------------------------------------------------- */
1978
1979   level_subdir = leveldir_current->filename;
1980
1981   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1982
1983   if ((level_setup_list = loadSetupFileList(filename)))
1984   {
1985     char *token_value;
1986
1987     token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1988
1989     if (token_value)
1990     {
1991       level_nr = atoi(token_value);
1992
1993       if (level_nr < leveldir_current->first_level)
1994         level_nr = leveldir_current->first_level;
1995       if (level_nr > leveldir_current->last_level)
1996         level_nr = leveldir_current->last_level;
1997     }
1998
1999     token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
2000
2001     if (token_value)
2002     {
2003       int level_nr = atoi(token_value);
2004
2005       if (level_nr < leveldir_current->first_level)
2006         level_nr = leveldir_current->first_level;
2007       if (level_nr > leveldir_current->last_level + 1)
2008         level_nr = leveldir_current->last_level;
2009
2010       if (leveldir_current->user_defined)
2011         level_nr = leveldir_current->last_level;
2012
2013       leveldir_current->handicap_level = level_nr;
2014     }
2015
2016     checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
2017
2018     freeSetupFileList(level_setup_list);
2019   }
2020   else
2021     Error(ERR_WARN, "using default setup values");
2022
2023   free(filename);
2024 }
2025
2026 void SaveLevelSetup_SeriesInfo()
2027 {
2028   char *filename;
2029   char *level_subdir = leveldir_current->filename;
2030   char *level_nr_str = int2str(level_nr, 0);
2031   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2032   FILE *file;
2033
2034   /* ----------------------------------------------------------------------- */
2035   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2036   /* ----------------------------------------------------------------------- */
2037
2038   InitLevelSetupDirectory(level_subdir);
2039
2040   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2041
2042   if (!(file = fopen(filename, MODE_WRITE)))
2043   {
2044     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2045     free(filename);
2046     return;
2047   }
2048
2049   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2050                                                  getCookie("LEVELSETUP")));
2051   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2052                                                level_nr_str));
2053   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2054                                                handicap_level_str));
2055
2056   fclose(file);
2057   free(filename);
2058
2059   SetFilePermissions(filename, PERMS_PRIVATE);
2060 }