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