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