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 
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
93 
94 static int token_value_position   = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
96 
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
100 
101 
102 /* ------------------------------------------------------------------------- */
103 /* file functions                                                            */
104 /* ------------------------------------------------------------------------- */
105 
getLevelClassDescription(TreeInfo * ti)106 static char *getLevelClassDescription(TreeInfo *ti)
107 {
108   int position = ti->sort_priority / 100;
109 
110   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111     return levelclass_desc[position];
112   else
113     return "Unknown Level Class";
114 }
115 
getUserLevelDir(char * level_subdir)116 static char *getUserLevelDir(char *level_subdir)
117 {
118   static char *userlevel_dir = NULL;
119   char *data_dir = getUserGameDataDir();
120   char *userlevel_subdir = LEVELS_DIRECTORY;
121 
122   checked_free(userlevel_dir);
123 
124   if (level_subdir != NULL)
125     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
126   else
127     userlevel_dir = getPath2(data_dir, userlevel_subdir);
128 
129   return userlevel_dir;
130 }
131 
getScoreDir(char * level_subdir)132 static char *getScoreDir(char *level_subdir)
133 {
134   static char *score_dir = NULL;
135   char *data_dir = getCommonDataDir();
136   char *score_subdir = SCORES_DIRECTORY;
137 
138   checked_free(score_dir);
139 
140   if (level_subdir != NULL)
141     score_dir = getPath3(data_dir, score_subdir, level_subdir);
142   else
143     score_dir = getPath2(data_dir, score_subdir);
144 
145   return score_dir;
146 }
147 
getLevelSetupDir(char * level_subdir)148 static char *getLevelSetupDir(char *level_subdir)
149 {
150   static char *levelsetup_dir = NULL;
151   char *data_dir = getUserGameDataDir();
152   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
153 
154   checked_free(levelsetup_dir);
155 
156   if (level_subdir != NULL)
157     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
158   else
159     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
160 
161   return levelsetup_dir;
162 }
163 
getCacheDir()164 static char *getCacheDir()
165 {
166   static char *cache_dir = NULL;
167 
168   if (cache_dir == NULL)
169     cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
170 
171   return cache_dir;
172 }
173 
getLevelDirFromTreeInfo(TreeInfo * node)174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
175 {
176   static char *level_dir = NULL;
177 
178   if (node == NULL)
179     return options.level_directory;
180 
181   checked_free(level_dir);
182 
183   level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 			options.level_directory), node->fullpath);
185 
186   return level_dir;
187 }
188 
getCurrentLevelDir()189 char *getCurrentLevelDir()
190 {
191   return getLevelDirFromTreeInfo(leveldir_current);
192 }
193 
getTapeDir(char * level_subdir)194 static char *getTapeDir(char *level_subdir)
195 {
196   static char *tape_dir = NULL;
197   char *data_dir = getUserGameDataDir();
198   char *tape_subdir = TAPES_DIRECTORY;
199 
200   checked_free(tape_dir);
201 
202   if (level_subdir != NULL)
203     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
204   else
205     tape_dir = getPath2(data_dir, tape_subdir);
206 
207   return tape_dir;
208 }
209 
getSolutionTapeDir()210 static char *getSolutionTapeDir()
211 {
212   static char *tape_dir = NULL;
213   char *data_dir = getCurrentLevelDir();
214   char *tape_subdir = TAPES_DIRECTORY;
215 
216   checked_free(tape_dir);
217 
218   tape_dir = getPath2(data_dir, tape_subdir);
219 
220   return tape_dir;
221 }
222 
getDefaultGraphicsDir(char * graphics_subdir)223 static char *getDefaultGraphicsDir(char *graphics_subdir)
224 {
225   static char *graphics_dir = NULL;
226 
227   if (graphics_subdir == NULL)
228     return options.graphics_directory;
229 
230   checked_free(graphics_dir);
231 
232   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
233 
234   return graphics_dir;
235 }
236 
getDefaultSoundsDir(char * sounds_subdir)237 static char *getDefaultSoundsDir(char *sounds_subdir)
238 {
239   static char *sounds_dir = NULL;
240 
241   if (sounds_subdir == NULL)
242     return options.sounds_directory;
243 
244   checked_free(sounds_dir);
245 
246   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
247 
248   return sounds_dir;
249 }
250 
getDefaultMusicDir(char * music_subdir)251 static char *getDefaultMusicDir(char *music_subdir)
252 {
253   static char *music_dir = NULL;
254 
255   if (music_subdir == NULL)
256     return options.music_directory;
257 
258   checked_free(music_dir);
259 
260   music_dir = getPath2(options.music_directory, music_subdir);
261 
262   return music_dir;
263 }
264 
getClassicArtworkSet(int type)265 static char *getClassicArtworkSet(int type)
266 {
267   return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 	  type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
269 	  type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
270 }
271 
getClassicArtworkDir(int type)272 static char *getClassicArtworkDir(int type)
273 {
274   return (type == TREE_TYPE_GRAPHICS_DIR ?
275 	  getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 	  type == TREE_TYPE_SOUNDS_DIR ?
277 	  getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 	  type == TREE_TYPE_MUSIC_DIR ?
279 	  getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
280 }
281 
getUserGraphicsDir()282 static char *getUserGraphicsDir()
283 {
284   static char *usergraphics_dir = NULL;
285 
286   if (usergraphics_dir == NULL)
287     usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
288 
289   return usergraphics_dir;
290 }
291 
getUserSoundsDir()292 static char *getUserSoundsDir()
293 {
294   static char *usersounds_dir = NULL;
295 
296   if (usersounds_dir == NULL)
297     usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
298 
299   return usersounds_dir;
300 }
301 
getUserMusicDir()302 static char *getUserMusicDir()
303 {
304   static char *usermusic_dir = NULL;
305 
306   if (usermusic_dir == NULL)
307     usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
308 
309   return usermusic_dir;
310 }
311 
getSetupArtworkDir(TreeInfo * ti)312 static char *getSetupArtworkDir(TreeInfo *ti)
313 {
314   static char *artwork_dir = NULL;
315 
316   checked_free(artwork_dir);
317 
318   artwork_dir = getPath2(ti->basepath, ti->fullpath);
319 
320   return artwork_dir;
321 }
322 
setLevelArtworkDir(TreeInfo * ti)323 char *setLevelArtworkDir(TreeInfo *ti)
324 {
325   char **artwork_path_ptr, **artwork_set_ptr;
326   TreeInfo *level_artwork;
327 
328   if (ti == NULL || leveldir_current == NULL)
329     return NULL;
330 
331   artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332   artwork_set_ptr  = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
333 
334   checked_free(*artwork_path_ptr);
335 
336   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337   {
338     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
339   }
340   else
341   {
342     /*
343       No (or non-existing) artwork configured in "levelinfo.conf". This would
344       normally result in using the artwork configured in the setup menu. But
345       if an artwork subdirectory exists (which might contain custom artwork
346       or an artwork configuration file), this level artwork must be treated
347       as relative to the default "classic" artwork, not to the artwork that
348       is currently configured in the setup menu.
349 
350       Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
351       the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
352       the real "classic" artwork from the original R'n'D (like "gfx_classic").
353     */
354 
355     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
356 
357     checked_free(*artwork_set_ptr);
358 
359     if (fileExists(dir))
360     {
361       *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
362       *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
363     }
364     else
365     {
366       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
367       *artwork_set_ptr = NULL;
368     }
369 
370     free(dir);
371   }
372 
373   return *artwork_set_ptr;
374 }
375 
getLevelArtworkSet(int type)376 inline static char *getLevelArtworkSet(int type)
377 {
378   if (leveldir_current == NULL)
379     return NULL;
380 
381   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
382 }
383 
getLevelArtworkDir(int type)384 inline static char *getLevelArtworkDir(int type)
385 {
386   if (leveldir_current == NULL)
387     return UNDEFINED_FILENAME;
388 
389   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
390 }
391 
getTapeFilename(int nr)392 char *getTapeFilename(int nr)
393 {
394   static char *filename = NULL;
395   char basename[MAX_FILENAME_LEN];
396 
397   checked_free(filename);
398 
399   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
400   filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
401 
402   return filename;
403 }
404 
getSolutionTapeFilename(int nr)405 char *getSolutionTapeFilename(int nr)
406 {
407   static char *filename = NULL;
408   char basename[MAX_FILENAME_LEN];
409 
410   checked_free(filename);
411 
412   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
413   filename = getPath2(getSolutionTapeDir(), basename);
414 
415   if (!fileExists(filename))
416   {
417     static char *filename_sln = NULL;
418 
419     checked_free(filename_sln);
420 
421     sprintf(basename, "%03d.sln", nr);
422     filename_sln = getPath2(getSolutionTapeDir(), basename);
423 
424     if (fileExists(filename_sln))
425       return filename_sln;
426   }
427 
428   return filename;
429 }
430 
getScoreFilename(int nr)431 char *getScoreFilename(int nr)
432 {
433   static char *filename = NULL;
434   char basename[MAX_FILENAME_LEN];
435 
436   checked_free(filename);
437 
438   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
439   filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
440 
441   return filename;
442 }
443 
getSetupFilename()444 char *getSetupFilename()
445 {
446   static char *filename = NULL;
447 
448   checked_free(filename);
449 
450   filename = getPath2(getSetupDir(), SETUP_FILENAME);
451 
452   return filename;
453 }
454 
getEditorSetupFilename()455 char *getEditorSetupFilename()
456 {
457   static char *filename = NULL;
458 
459   checked_free(filename);
460   filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
461 
462   if (fileExists(filename))
463     return filename;
464 
465   checked_free(filename);
466   filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
467 
468   return filename;
469 }
470 
getHelpAnimFilename()471 char *getHelpAnimFilename()
472 {
473   static char *filename = NULL;
474 
475   checked_free(filename);
476 
477   filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
478 
479   return filename;
480 }
481 
getHelpTextFilename()482 char *getHelpTextFilename()
483 {
484   static char *filename = NULL;
485 
486   checked_free(filename);
487 
488   filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
489 
490   return filename;
491 }
492 
getLevelSetInfoFilename()493 char *getLevelSetInfoFilename()
494 {
495   static char *filename = NULL;
496   char *basenames[] =
497   {
498     "README",
499     "README.TXT",
500     "README.txt",
501     "Readme",
502     "Readme.txt",
503     "readme",
504     "readme.txt",
505 
506     NULL
507   };
508   int i;
509 
510   for (i = 0; basenames[i] != NULL; i++)
511   {
512     checked_free(filename);
513     filename = getPath2(getCurrentLevelDir(), basenames[i]);
514 
515     if (fileExists(filename))
516       return filename;
517   }
518 
519   return NULL;
520 }
521 
getLevelSetTitleMessageBasename(int nr,boolean initial)522 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
523 {
524   static char basename[32];
525 
526   sprintf(basename, "%s_%d.txt",
527 	  (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
528 
529   return basename;
530 }
531 
getLevelSetTitleMessageFilename(int nr,boolean initial)532 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
533 {
534   static char *filename = NULL;
535   char *basename;
536   boolean skip_setup_artwork = FALSE;
537 
538   checked_free(filename);
539 
540   basename = getLevelSetTitleMessageBasename(nr, initial);
541 
542   if (!gfx.override_level_graphics)
543   {
544     /* 1st try: look for special artwork in current level series directory */
545     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
546     if (fileExists(filename))
547       return filename;
548 
549     free(filename);
550 
551     /* 2nd try: look for message file in current level set directory */
552     filename = getPath2(getCurrentLevelDir(), basename);
553     if (fileExists(filename))
554       return filename;
555 
556     free(filename);
557 
558     /* check if there is special artwork configured in level series config */
559     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
560     {
561       /* 3rd try: look for special artwork configured in level series config */
562       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
563       if (fileExists(filename))
564 	return filename;
565 
566       free(filename);
567 
568       /* take missing artwork configured in level set config from default */
569       skip_setup_artwork = TRUE;
570     }
571   }
572 
573   if (!skip_setup_artwork)
574   {
575     /* 4th try: look for special artwork in configured artwork directory */
576     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
577     if (fileExists(filename))
578       return filename;
579 
580     free(filename);
581   }
582 
583   /* 5th try: look for default artwork in new default artwork directory */
584   filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
585   if (fileExists(filename))
586     return filename;
587 
588   free(filename);
589 
590   /* 6th try: look for default artwork in old default artwork directory */
591   filename = getPath2(options.graphics_directory, basename);
592   if (fileExists(filename))
593     return filename;
594 
595   return NULL;		/* cannot find specified artwork file anywhere */
596 }
597 
getCorrectedArtworkBasename(char * basename)598 static char *getCorrectedArtworkBasename(char *basename)
599 {
600   char *basename_corrected = basename;
601 
602 #if defined(PLATFORM_MSDOS)
603   if (program.filename_prefix != NULL)
604   {
605     int prefix_len = strlen(program.filename_prefix);
606 
607     if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
608       basename_corrected = &basename[prefix_len];
609 
610     /* if corrected filename is still longer than standard MS-DOS filename
611        size (8 characters + 1 dot + 3 characters file extension), shorten
612        filename by writing file extension after 8th basename character */
613     if (strlen(basename_corrected) > 8 + 1 + 3)
614     {
615       static char *msdos_filename = NULL;
616 
617       checked_free(msdos_filename);
618 
619       msdos_filename = getStringCopy(basename_corrected);
620       strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
621 
622       basename_corrected = msdos_filename;
623     }
624   }
625 #endif
626 
627   return basename_corrected;
628 }
629 
getCustomImageFilename(char * basename)630 char *getCustomImageFilename(char *basename)
631 {
632   static char *filename = NULL;
633   boolean skip_setup_artwork = FALSE;
634 
635   checked_free(filename);
636 
637   basename = getCorrectedArtworkBasename(basename);
638 
639   if (!gfx.override_level_graphics)
640   {
641     /* 1st try: look for special artwork in current level series directory */
642     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
643     if (fileExists(filename))
644       return filename;
645 
646     free(filename);
647 
648     /* check if there is special artwork configured in level series config */
649     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
650     {
651       /* 2nd try: look for special artwork configured in level series config */
652       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
653       if (fileExists(filename))
654 	return filename;
655 
656       free(filename);
657 
658       /* take missing artwork configured in level set config from default */
659       skip_setup_artwork = TRUE;
660     }
661   }
662 
663   if (!skip_setup_artwork)
664   {
665     /* 3rd try: look for special artwork in configured artwork directory */
666     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
667     if (fileExists(filename))
668       return filename;
669 
670     free(filename);
671   }
672 
673   /* 4th try: look for default artwork in new default artwork directory */
674   filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
675   if (fileExists(filename))
676     return filename;
677 
678   free(filename);
679 
680   /* 5th try: look for default artwork in old default artwork directory */
681   filename = getPath2(options.graphics_directory, basename);
682   if (fileExists(filename))
683     return filename;
684 
685 #if defined(CREATE_SPECIAL_EDITION)
686   free(filename);
687 
688   if (options.debug)
689     Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
690 
691   /* 6th try: look for fallback artwork in old default artwork directory */
692   /* (needed to prevent errors when trying to access unused artwork files) */
693   filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
694   if (fileExists(filename))
695     return filename;
696 #endif
697 
698   return NULL;		/* cannot find specified artwork file anywhere */
699 }
700 
getCustomSoundFilename(char * basename)701 char *getCustomSoundFilename(char *basename)
702 {
703   static char *filename = NULL;
704   boolean skip_setup_artwork = FALSE;
705 
706   checked_free(filename);
707 
708   basename = getCorrectedArtworkBasename(basename);
709 
710   if (!gfx.override_level_sounds)
711   {
712     /* 1st try: look for special artwork in current level series directory */
713     filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
714     if (fileExists(filename))
715       return filename;
716 
717     free(filename);
718 
719     /* check if there is special artwork configured in level series config */
720     if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
721     {
722       /* 2nd try: look for special artwork configured in level series config */
723       filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
724       if (fileExists(filename))
725 	return filename;
726 
727       free(filename);
728 
729       /* take missing artwork configured in level set config from default */
730       skip_setup_artwork = TRUE;
731     }
732   }
733 
734   if (!skip_setup_artwork)
735   {
736     /* 3rd try: look for special artwork in configured artwork directory */
737     filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
738     if (fileExists(filename))
739       return filename;
740 
741     free(filename);
742   }
743 
744   /* 4th try: look for default artwork in new default artwork directory */
745   filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
746   if (fileExists(filename))
747     return filename;
748 
749   free(filename);
750 
751   /* 5th try: look for default artwork in old default artwork directory */
752   filename = getPath2(options.sounds_directory, basename);
753   if (fileExists(filename))
754     return filename;
755 
756 #if defined(CREATE_SPECIAL_EDITION)
757   free(filename);
758 
759   if (options.debug)
760     Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
761 
762   /* 6th try: look for fallback artwork in old default artwork directory */
763   /* (needed to prevent errors when trying to access unused artwork files) */
764   filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
765   if (fileExists(filename))
766     return filename;
767 #endif
768 
769   return NULL;		/* cannot find specified artwork file anywhere */
770 }
771 
getCustomMusicFilename(char * basename)772 char *getCustomMusicFilename(char *basename)
773 {
774   static char *filename = NULL;
775   boolean skip_setup_artwork = FALSE;
776 
777   checked_free(filename);
778 
779   basename = getCorrectedArtworkBasename(basename);
780 
781   if (!gfx.override_level_music)
782   {
783     /* 1st try: look for special artwork in current level series directory */
784     filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
785     if (fileExists(filename))
786       return filename;
787 
788     free(filename);
789 
790     /* check if there is special artwork configured in level series config */
791     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
792     {
793       /* 2nd try: look for special artwork configured in level series config */
794       filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
795       if (fileExists(filename))
796 	return filename;
797 
798       free(filename);
799 
800       /* take missing artwork configured in level set config from default */
801       skip_setup_artwork = TRUE;
802     }
803   }
804 
805   if (!skip_setup_artwork)
806   {
807     /* 3rd try: look for special artwork in configured artwork directory */
808     filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
809     if (fileExists(filename))
810       return filename;
811 
812     free(filename);
813   }
814 
815   /* 4th try: look for default artwork in new default artwork directory */
816   filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
817   if (fileExists(filename))
818     return filename;
819 
820   free(filename);
821 
822   /* 5th try: look for default artwork in old default artwork directory */
823   filename = getPath2(options.music_directory, basename);
824   if (fileExists(filename))
825     return filename;
826 
827 #if defined(CREATE_SPECIAL_EDITION)
828   free(filename);
829 
830   if (options.debug)
831     Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
832 
833   /* 6th try: look for fallback artwork in old default artwork directory */
834   /* (needed to prevent errors when trying to access unused artwork files) */
835   filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
836   if (fileExists(filename))
837     return filename;
838 #endif
839 
840   return NULL;		/* cannot find specified artwork file anywhere */
841 }
842 
getCustomArtworkFilename(char * basename,int type)843 char *getCustomArtworkFilename(char *basename, int type)
844 {
845   if (type == ARTWORK_TYPE_GRAPHICS)
846     return getCustomImageFilename(basename);
847   else if (type == ARTWORK_TYPE_SOUNDS)
848     return getCustomSoundFilename(basename);
849   else if (type == ARTWORK_TYPE_MUSIC)
850     return getCustomMusicFilename(basename);
851   else
852     return UNDEFINED_FILENAME;
853 }
854 
getCustomArtworkConfigFilename(int type)855 char *getCustomArtworkConfigFilename(int type)
856 {
857   return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
858 }
859 
getCustomArtworkLevelConfigFilename(int type)860 char *getCustomArtworkLevelConfigFilename(int type)
861 {
862   static char *filename = NULL;
863 
864   checked_free(filename);
865 
866   filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
867 
868   return filename;
869 }
870 
getCustomMusicDirectory(void)871 char *getCustomMusicDirectory(void)
872 {
873   static char *directory = NULL;
874   boolean skip_setup_artwork = FALSE;
875 
876   checked_free(directory);
877 
878   if (!gfx.override_level_music)
879   {
880     /* 1st try: look for special artwork in current level series directory */
881     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
882     if (fileExists(directory))
883       return directory;
884 
885     free(directory);
886 
887     /* check if there is special artwork configured in level series config */
888     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
889     {
890       /* 2nd try: look for special artwork configured in level series config */
891       directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
892       if (fileExists(directory))
893 	return directory;
894 
895       free(directory);
896 
897       /* take missing artwork configured in level set config from default */
898       skip_setup_artwork = TRUE;
899     }
900   }
901 
902   if (!skip_setup_artwork)
903   {
904     /* 3rd try: look for special artwork in configured artwork directory */
905     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
906     if (fileExists(directory))
907       return directory;
908 
909     free(directory);
910   }
911 
912   /* 4th try: look for default artwork in new default artwork directory */
913   directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
914   if (fileExists(directory))
915     return directory;
916 
917   free(directory);
918 
919   /* 5th try: look for default artwork in old default artwork directory */
920   directory = getStringCopy(options.music_directory);
921   if (fileExists(directory))
922     return directory;
923 
924   return NULL;		/* cannot find specified artwork file anywhere */
925 }
926 
InitTapeDirectory(char * level_subdir)927 void InitTapeDirectory(char *level_subdir)
928 {
929   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
930   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
931   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
932 }
933 
InitScoreDirectory(char * level_subdir)934 void InitScoreDirectory(char *level_subdir)
935 {
936   createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
937   createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
938   createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
939 }
940 
941 static void SaveUserLevelInfo();
942 
InitUserLevelDirectory(char * level_subdir)943 void InitUserLevelDirectory(char *level_subdir)
944 {
945   if (!fileExists(getUserLevelDir(level_subdir)))
946   {
947     createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
948     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
949     createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
950 
951     SaveUserLevelInfo();
952   }
953 }
954 
InitLevelSetupDirectory(char * level_subdir)955 void InitLevelSetupDirectory(char *level_subdir)
956 {
957   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
958   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
959   createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
960 }
961 
InitCacheDirectory()962 void InitCacheDirectory()
963 {
964   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
965   createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
966 }
967 
968 
969 /* ------------------------------------------------------------------------- */
970 /* some functions to handle lists of level and artwork directories           */
971 /* ------------------------------------------------------------------------- */
972 
newTreeInfo()973 TreeInfo *newTreeInfo()
974 {
975   return checked_calloc(sizeof(TreeInfo));
976 }
977 
newTreeInfo_setDefaults(int type)978 TreeInfo *newTreeInfo_setDefaults(int type)
979 {
980   TreeInfo *ti = newTreeInfo();
981 
982   setTreeInfoToDefaults(ti, type);
983 
984   return ti;
985 }
986 
pushTreeInfo(TreeInfo ** node_first,TreeInfo * node_new)987 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
988 {
989   node_new->next = *node_first;
990   *node_first = node_new;
991 }
992 
numTreeInfo(TreeInfo * node)993 int numTreeInfo(TreeInfo *node)
994 {
995   int num = 0;
996 
997   while (node)
998   {
999     num++;
1000     node = node->next;
1001   }
1002 
1003   return num;
1004 }
1005 
validLevelSeries(TreeInfo * node)1006 boolean validLevelSeries(TreeInfo *node)
1007 {
1008   return (node != NULL && !node->node_group && !node->parent_link);
1009 }
1010 
getFirstValidTreeInfoEntry(TreeInfo * node)1011 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1012 {
1013   if (node == NULL)
1014     return NULL;
1015 
1016   if (node->node_group)		/* enter level group (step down into tree) */
1017     return getFirstValidTreeInfoEntry(node->node_group);
1018   else if (node->parent_link)	/* skip start entry of level group */
1019   {
1020     if (node->next)		/* get first real level series entry */
1021       return getFirstValidTreeInfoEntry(node->next);
1022     else			/* leave empty level group and go on */
1023       return getFirstValidTreeInfoEntry(node->node_parent->next);
1024   }
1025   else				/* this seems to be a regular level series */
1026     return node;
1027 }
1028 
getTreeInfoFirstGroupEntry(TreeInfo * node)1029 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1030 {
1031   if (node == NULL)
1032     return NULL;
1033 
1034   if (node->node_parent == NULL)		/* top level group */
1035     return *node->node_top;
1036   else						/* sub level group */
1037     return node->node_parent->node_group;
1038 }
1039 
numTreeInfoInGroup(TreeInfo * node)1040 int numTreeInfoInGroup(TreeInfo *node)
1041 {
1042   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1043 }
1044 
posTreeInfo(TreeInfo * node)1045 int posTreeInfo(TreeInfo *node)
1046 {
1047   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1048   int pos = 0;
1049 
1050   while (node_cmp)
1051   {
1052     if (node_cmp == node)
1053       return pos;
1054 
1055     pos++;
1056     node_cmp = node_cmp->next;
1057   }
1058 
1059   return 0;
1060 }
1061 
getTreeInfoFromPos(TreeInfo * node,int pos)1062 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1063 {
1064   TreeInfo *node_default = node;
1065   int pos_cmp = 0;
1066 
1067   while (node)
1068   {
1069     if (pos_cmp == pos)
1070       return node;
1071 
1072     pos_cmp++;
1073     node = node->next;
1074   }
1075 
1076   return node_default;
1077 }
1078 
getTreeInfoFromIdentifier(TreeInfo * node,char * identifier)1079 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1080 {
1081   if (identifier == NULL)
1082     return NULL;
1083 
1084   while (node)
1085   {
1086     if (node->node_group)
1087     {
1088       TreeInfo *node_group;
1089 
1090       node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1091 
1092       if (node_group)
1093 	return node_group;
1094     }
1095     else if (!node->parent_link)
1096     {
1097       if (strEqual(identifier, node->identifier))
1098 	return node;
1099     }
1100 
1101     node = node->next;
1102   }
1103 
1104   return NULL;
1105 }
1106 
cloneTreeNode(TreeInfo ** node_top,TreeInfo * node_parent,TreeInfo * node,boolean skip_sets_without_levels)1107 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1108 			TreeInfo *node, boolean skip_sets_without_levels)
1109 {
1110   TreeInfo *node_new;
1111 
1112   if (node == NULL)
1113     return NULL;
1114 
1115   if (!node->parent_link && !node->level_group &&
1116       skip_sets_without_levels && node->levels == 0)
1117     return cloneTreeNode(node_top, node_parent, node->next,
1118 			 skip_sets_without_levels);
1119 
1120 #if 1
1121   node_new = getTreeInfoCopy(node);		/* copy complete node */
1122 #else
1123   node_new = newTreeInfo();
1124 
1125   *node_new = *node;				/* copy complete node */
1126 #endif
1127 
1128   node_new->node_top = node_top;		/* correct top node link */
1129   node_new->node_parent = node_parent;		/* correct parent node link */
1130 
1131   if (node->level_group)
1132     node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1133 					 skip_sets_without_levels);
1134 
1135   node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1136 				 skip_sets_without_levels);
1137 
1138   return node_new;
1139 }
1140 
cloneTree(TreeInfo ** ti_new,TreeInfo * ti,boolean skip_empty_sets)1141 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1142 {
1143   TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1144 
1145   *ti_new = ti_cloned;
1146 }
1147 
adjustTreeGraphicsForEMC(TreeInfo * node)1148 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1149 {
1150   boolean settings_changed = FALSE;
1151 
1152   while (node)
1153   {
1154     if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1155 	!strEqual(node->graphics_set, node->graphics_set_ecs))
1156     {
1157       setString(&node->graphics_set, node->graphics_set_ecs);
1158       settings_changed = TRUE;
1159     }
1160     else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1161 	     !strEqual(node->graphics_set, node->graphics_set_aga))
1162     {
1163       setString(&node->graphics_set, node->graphics_set_aga);
1164       settings_changed = TRUE;
1165     }
1166 
1167     if (node->node_group != NULL)
1168       settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1169 
1170     node = node->next;
1171   }
1172 
1173   return settings_changed;
1174 }
1175 
dumpTreeInfo(TreeInfo * node,int depth)1176 void dumpTreeInfo(TreeInfo *node, int depth)
1177 {
1178   int i;
1179 
1180   printf("Dumping TreeInfo:\n");
1181 
1182   while (node)
1183   {
1184     for (i = 0; i < (depth + 1) * 3; i++)
1185       printf(" ");
1186 
1187     printf("'%s' / '%s'\n", node->identifier, node->name);
1188 
1189     /*
1190     // use for dumping artwork info tree
1191     printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1192 	   node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1193     */
1194 
1195     if (node->node_group != NULL)
1196       dumpTreeInfo(node->node_group, depth + 1);
1197 
1198     node = node->next;
1199   }
1200 }
1201 
sortTreeInfoBySortFunction(TreeInfo ** node_first,int (* compare_function)(const void *,const void *))1202 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1203 				int (*compare_function)(const void *,
1204 							const void *))
1205 {
1206   int num_nodes = numTreeInfo(*node_first);
1207   TreeInfo **sort_array;
1208   TreeInfo *node = *node_first;
1209   int i = 0;
1210 
1211   if (num_nodes == 0)
1212     return;
1213 
1214   /* allocate array for sorting structure pointers */
1215   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1216 
1217   /* writing structure pointers to sorting array */
1218   while (i < num_nodes && node)		/* double boundary check... */
1219   {
1220     sort_array[i] = node;
1221 
1222     i++;
1223     node = node->next;
1224   }
1225 
1226   /* sorting the structure pointers in the sorting array */
1227   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1228 	compare_function);
1229 
1230   /* update the linkage of list elements with the sorted node array */
1231   for (i = 0; i < num_nodes - 1; i++)
1232     sort_array[i]->next = sort_array[i + 1];
1233   sort_array[num_nodes - 1]->next = NULL;
1234 
1235   /* update the linkage of the main list anchor pointer */
1236   *node_first = sort_array[0];
1237 
1238   free(sort_array);
1239 
1240   /* now recursively sort the level group structures */
1241   node = *node_first;
1242   while (node)
1243   {
1244     if (node->node_group != NULL)
1245       sortTreeInfoBySortFunction(&node->node_group, compare_function);
1246 
1247     node = node->next;
1248   }
1249 }
1250 
sortTreeInfo(TreeInfo ** node_first)1251 void sortTreeInfo(TreeInfo **node_first)
1252 {
1253   sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1254 }
1255 
1256 
1257 /* ========================================================================= */
1258 /* some stuff from "files.c"                                                 */
1259 /* ========================================================================= */
1260 
1261 #if defined(PLATFORM_WIN32)
1262 #ifndef S_IRGRP
1263 #define S_IRGRP S_IRUSR
1264 #endif
1265 #ifndef S_IROTH
1266 #define S_IROTH S_IRUSR
1267 #endif
1268 #ifndef S_IWGRP
1269 #define S_IWGRP S_IWUSR
1270 #endif
1271 #ifndef S_IWOTH
1272 #define S_IWOTH S_IWUSR
1273 #endif
1274 #ifndef S_IXGRP
1275 #define S_IXGRP S_IXUSR
1276 #endif
1277 #ifndef S_IXOTH
1278 #define S_IXOTH S_IXUSR
1279 #endif
1280 #ifndef S_IRWXG
1281 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1282 #endif
1283 #ifndef S_ISGID
1284 #define S_ISGID 0
1285 #endif
1286 #endif	/* PLATFORM_WIN32 */
1287 
1288 /* file permissions for newly written files */
1289 #define MODE_R_ALL		(S_IRUSR | S_IRGRP | S_IROTH)
1290 #define MODE_W_ALL		(S_IWUSR | S_IWGRP | S_IWOTH)
1291 #define MODE_X_ALL		(S_IXUSR | S_IXGRP | S_IXOTH)
1292 
1293 #define MODE_W_PRIVATE		(S_IWUSR)
1294 #define MODE_W_PUBLIC		(S_IWUSR | S_IWGRP)
1295 #define MODE_W_PUBLIC_DIR	(S_IWUSR | S_IWGRP | S_ISGID)
1296 
1297 #define DIR_PERMS_PRIVATE	(MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1298 #define DIR_PERMS_PUBLIC	(MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1299 
1300 #define FILE_PERMS_PRIVATE	(MODE_R_ALL | MODE_W_PRIVATE)
1301 #define FILE_PERMS_PUBLIC	(MODE_R_ALL | MODE_W_PUBLIC)
1302 
getHomeDir()1303 char *getHomeDir()
1304 {
1305   static char *dir = NULL;
1306 
1307 #if defined(PLATFORM_WIN32)
1308   if (dir == NULL)
1309   {
1310     dir = checked_malloc(MAX_PATH + 1);
1311 
1312     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1313       strcpy(dir, ".");
1314   }
1315 #elif defined(PLATFORM_UNIX)
1316   if (dir == NULL)
1317   {
1318     if ((dir = getenv("HOME")) == NULL)
1319     {
1320       struct passwd *pwd;
1321 
1322       if ((pwd = getpwuid(getuid())) != NULL)
1323 	dir = getStringCopy(pwd->pw_dir);
1324       else
1325 	dir = ".";
1326     }
1327   }
1328 #else
1329   dir = ".";
1330 #endif
1331 
1332   return dir;
1333 }
1334 
getCommonDataDir(void)1335 char *getCommonDataDir(void)
1336 {
1337   static char *common_data_dir = NULL;
1338 
1339 #if defined(PLATFORM_WIN32)
1340   if (common_data_dir == NULL)
1341   {
1342     char *dir = checked_malloc(MAX_PATH + 1);
1343 
1344     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1345 	&& !strEqual(dir, ""))		/* empty for Windows 95/98 */
1346       common_data_dir = getPath2(dir, program.userdata_subdir);
1347     else
1348       common_data_dir = options.rw_base_directory;
1349   }
1350 #else
1351   if (common_data_dir == NULL)
1352     common_data_dir = options.rw_base_directory;
1353 #endif
1354 
1355   return common_data_dir;
1356 }
1357 
getPersonalDataDir(void)1358 char *getPersonalDataDir(void)
1359 {
1360   static char *personal_data_dir = NULL;
1361 
1362 #if defined(PLATFORM_MACOSX)
1363   if (personal_data_dir == NULL)
1364     personal_data_dir = getPath2(getHomeDir(), "Documents");
1365 #else
1366   if (personal_data_dir == NULL)
1367     personal_data_dir = getHomeDir();
1368 #endif
1369 
1370   return personal_data_dir;
1371 }
1372 
getUserGameDataDir(void)1373 char *getUserGameDataDir(void)
1374 {
1375   static char *user_game_data_dir = NULL;
1376 
1377   if (user_game_data_dir == NULL)
1378     user_game_data_dir = getPath2(getPersonalDataDir(),
1379 				  program.userdata_subdir);
1380 
1381   return user_game_data_dir;
1382 }
1383 
updateUserGameDataDir()1384 void updateUserGameDataDir()
1385 {
1386 #if defined(PLATFORM_MACOSX)
1387   char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1388   char *userdata_dir_new = getUserGameDataDir();	/* do not free() this */
1389 
1390   /* convert old Unix style game data directory to Mac OS X style, if needed */
1391   if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1392   {
1393     if (rename(userdata_dir_old, userdata_dir_new) != 0)
1394     {
1395       Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1396 	    userdata_dir_old, userdata_dir_new);
1397 
1398       /* continue using Unix style data directory -- this should not happen */
1399       program.userdata_path = getPath2(getPersonalDataDir(),
1400 				       program.userdata_subdir_unix);
1401     }
1402   }
1403 
1404   free(userdata_dir_old);
1405 #endif
1406 }
1407 
getSetupDir()1408 char *getSetupDir()
1409 {
1410   return getUserGameDataDir();
1411 }
1412 
posix_umask(mode_t mask)1413 static mode_t posix_umask(mode_t mask)
1414 {
1415 #if defined(PLATFORM_UNIX)
1416   return umask(mask);
1417 #else
1418   return 0;
1419 #endif
1420 }
1421 
posix_mkdir(const char * pathname,mode_t mode)1422 static int posix_mkdir(const char *pathname, mode_t mode)
1423 {
1424 #if defined(PLATFORM_WIN32)
1425   return mkdir(pathname);
1426 #else
1427   return mkdir(pathname, mode);
1428 #endif
1429 }
1430 
posix_process_running_setgid()1431 static boolean posix_process_running_setgid()
1432 {
1433 #if defined(PLATFORM_UNIX)
1434   return (getgid() != getegid());
1435 #else
1436   return FALSE;
1437 #endif
1438 }
1439 
createDirectory(char * dir,char * text,int permission_class)1440 void createDirectory(char *dir, char *text, int permission_class)
1441 {
1442   /* leave "other" permissions in umask untouched, but ensure group parts
1443      of USERDATA_DIR_MODE are not masked */
1444   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1445 		     DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1446   mode_t last_umask = posix_umask(0);
1447   mode_t group_umask = ~(dir_mode & S_IRWXG);
1448   int running_setgid = posix_process_running_setgid();
1449 
1450   /* if we're setgid, protect files against "other" */
1451   /* else keep umask(0) to make the dir world-writable */
1452 
1453   if (running_setgid)
1454     posix_umask(last_umask & group_umask);
1455   else
1456     dir_mode |= MODE_W_ALL;
1457 
1458   if (!fileExists(dir))
1459     if (posix_mkdir(dir, dir_mode) != 0)
1460       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1461 
1462   if (permission_class == PERMS_PUBLIC && !running_setgid)
1463     chmod(dir, dir_mode);
1464 
1465   posix_umask(last_umask);		/* restore previous umask */
1466 }
1467 
InitUserDataDirectory()1468 void InitUserDataDirectory()
1469 {
1470   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1471 }
1472 
SetFilePermissions(char * filename,int permission_class)1473 void SetFilePermissions(char *filename, int permission_class)
1474 {
1475   int running_setgid = posix_process_running_setgid();
1476   int perms = (permission_class == PERMS_PRIVATE ?
1477 	       FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1478 
1479   if (permission_class == PERMS_PUBLIC && !running_setgid)
1480     perms |= MODE_W_ALL;
1481 
1482   chmod(filename, perms);
1483 }
1484 
getCookie(char * file_type)1485 char *getCookie(char *file_type)
1486 {
1487   static char cookie[MAX_COOKIE_LEN + 1];
1488 
1489   if (strlen(program.cookie_prefix) + 1 +
1490       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1491     return "[COOKIE ERROR]";	/* should never happen */
1492 
1493   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1494 	  program.cookie_prefix, file_type,
1495 	  program.version_major, program.version_minor);
1496 
1497   return cookie;
1498 }
1499 
getFileVersionFromCookieString(const char * cookie)1500 int getFileVersionFromCookieString(const char *cookie)
1501 {
1502   const char *ptr_cookie1, *ptr_cookie2;
1503   const char *pattern1 = "_FILE_VERSION_";
1504   const char *pattern2 = "?.?";
1505   const int len_cookie = strlen(cookie);
1506   const int len_pattern1 = strlen(pattern1);
1507   const int len_pattern2 = strlen(pattern2);
1508   const int len_pattern = len_pattern1 + len_pattern2;
1509   int version_major, version_minor;
1510 
1511   if (len_cookie <= len_pattern)
1512     return -1;
1513 
1514   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1515   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1516 
1517   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1518     return -1;
1519 
1520   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1521       ptr_cookie2[1] != '.' ||
1522       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1523     return -1;
1524 
1525   version_major = ptr_cookie2[0] - '0';
1526   version_minor = ptr_cookie2[2] - '0';
1527 
1528   return VERSION_IDENT(version_major, version_minor, 0, 0);
1529 }
1530 
checkCookieString(const char * cookie,const char * template)1531 boolean checkCookieString(const char *cookie, const char *template)
1532 {
1533   const char *pattern = "_FILE_VERSION_?.?";
1534   const int len_cookie = strlen(cookie);
1535   const int len_template = strlen(template);
1536   const int len_pattern = strlen(pattern);
1537 
1538   if (len_cookie != len_template)
1539     return FALSE;
1540 
1541   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1542     return FALSE;
1543 
1544   return TRUE;
1545 }
1546 
1547 /* ------------------------------------------------------------------------- */
1548 /* setup file list and hash handling functions                               */
1549 /* ------------------------------------------------------------------------- */
1550 
getFormattedSetupEntry(char * token,char * value)1551 char *getFormattedSetupEntry(char *token, char *value)
1552 {
1553   int i;
1554   static char entry[MAX_LINE_LEN];
1555 
1556   /* if value is an empty string, just return token without value */
1557   if (*value == '\0')
1558     return token;
1559 
1560   /* start with the token and some spaces to format output line */
1561   sprintf(entry, "%s:", token);
1562   for (i = strlen(entry); i < token_value_position; i++)
1563     strcat(entry, " ");
1564 
1565   /* continue with the token's value */
1566   strcat(entry, value);
1567 
1568   return entry;
1569 }
1570 
newSetupFileList(char * token,char * value)1571 SetupFileList *newSetupFileList(char *token, char *value)
1572 {
1573   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1574 
1575   new->token = getStringCopy(token);
1576   new->value = getStringCopy(value);
1577 
1578   new->next = NULL;
1579 
1580   return new;
1581 }
1582 
freeSetupFileList(SetupFileList * list)1583 void freeSetupFileList(SetupFileList *list)
1584 {
1585   if (list == NULL)
1586     return;
1587 
1588   checked_free(list->token);
1589   checked_free(list->value);
1590 
1591   if (list->next)
1592     freeSetupFileList(list->next);
1593 
1594   free(list);
1595 }
1596 
getListEntry(SetupFileList * list,char * token)1597 char *getListEntry(SetupFileList *list, char *token)
1598 {
1599   if (list == NULL)
1600     return NULL;
1601 
1602   if (strEqual(list->token, token))
1603     return list->value;
1604   else
1605     return getListEntry(list->next, token);
1606 }
1607 
setListEntry(SetupFileList * list,char * token,char * value)1608 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1609 {
1610   if (list == NULL)
1611     return NULL;
1612 
1613   if (strEqual(list->token, token))
1614   {
1615     checked_free(list->value);
1616 
1617     list->value = getStringCopy(value);
1618 
1619     return list;
1620   }
1621   else if (list->next == NULL)
1622     return (list->next = newSetupFileList(token, value));
1623   else
1624     return setListEntry(list->next, token, value);
1625 }
1626 
addListEntry(SetupFileList * list,char * token,char * value)1627 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1628 {
1629   if (list == NULL)
1630     return NULL;
1631 
1632   if (list->next == NULL)
1633     return (list->next = newSetupFileList(token, value));
1634   else
1635     return addListEntry(list->next, token, value);
1636 }
1637 
1638 #if 0
1639 #ifdef DEBUG
1640 static void printSetupFileList(SetupFileList *list)
1641 {
1642   if (!list)
1643     return;
1644 
1645   printf("token: '%s'\n", list->token);
1646   printf("value: '%s'\n", list->value);
1647 
1648   printSetupFileList(list->next);
1649 }
1650 #endif
1651 #endif
1652 
1653 #ifdef DEBUG
1654 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1655 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1656 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1657 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1658 #else
1659 #define insert_hash_entry hashtable_insert
1660 #define search_hash_entry hashtable_search
1661 #define change_hash_entry hashtable_change
1662 #define remove_hash_entry hashtable_remove
1663 #endif
1664 
get_hash_from_key(void * key)1665 unsigned int get_hash_from_key(void *key)
1666 {
1667   /*
1668     djb2
1669 
1670     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1671     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1672     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1673     it works better than many other constants, prime or not) has never been
1674     adequately explained.
1675 
1676     If you just want to have a good hash function, and cannot wait, djb2
1677     is one of the best string hash functions i know. It has excellent
1678     distribution and speed on many different sets of keys and table sizes.
1679     You are not likely to do better with one of the "well known" functions
1680     such as PJW, K&R, etc.
1681 
1682     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1683   */
1684 
1685   char *str = (char *)key;
1686   unsigned int hash = 5381;
1687   int c;
1688 
1689   while ((c = *str++))
1690     hash = ((hash << 5) + hash) + c;	/* hash * 33 + c */
1691 
1692   return hash;
1693 }
1694 
keys_are_equal(void * key1,void * key2)1695 static int keys_are_equal(void *key1, void *key2)
1696 {
1697   return (strEqual((char *)key1, (char *)key2));
1698 }
1699 
newSetupFileHash()1700 SetupFileHash *newSetupFileHash()
1701 {
1702   SetupFileHash *new_hash =
1703     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1704 
1705   if (new_hash == NULL)
1706     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1707 
1708   return new_hash;
1709 }
1710 
freeSetupFileHash(SetupFileHash * hash)1711 void freeSetupFileHash(SetupFileHash *hash)
1712 {
1713   if (hash == NULL)
1714     return;
1715 
1716   hashtable_destroy(hash, 1);	/* 1 == also free values stored in hash */
1717 }
1718 
getHashEntry(SetupFileHash * hash,char * token)1719 char *getHashEntry(SetupFileHash *hash, char *token)
1720 {
1721   if (hash == NULL)
1722     return NULL;
1723 
1724   return search_hash_entry(hash, token);
1725 }
1726 
setHashEntry(SetupFileHash * hash,char * token,char * value)1727 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1728 {
1729   char *value_copy;
1730 
1731   if (hash == NULL)
1732     return;
1733 
1734   value_copy = getStringCopy(value);
1735 
1736   /* change value; if it does not exist, insert it as new */
1737   if (!change_hash_entry(hash, token, value_copy))
1738     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1739       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1740 }
1741 
removeHashEntry(SetupFileHash * hash,char * token)1742 char *removeHashEntry(SetupFileHash *hash, char *token)
1743 {
1744   if (hash == NULL)
1745     return NULL;
1746 
1747   return remove_hash_entry(hash, token);
1748 }
1749 
1750 #if 0
1751 static void printSetupFileHash(SetupFileHash *hash)
1752 {
1753   BEGIN_HASH_ITERATION(hash, itr)
1754   {
1755     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1756     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1757   }
1758   END_HASH_ITERATION(hash, itr)
1759 }
1760 #endif
1761 
1762 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE		1
1763 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING		0
1764 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH		0
1765 
1766 static boolean token_value_separator_found = FALSE;
1767 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1768 static boolean token_value_separator_warning = FALSE;
1769 #endif
1770 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1771 static boolean token_already_exists_warning = FALSE;
1772 #endif
1773 
getTokenValueFromSetupLineExt(char * line,char ** token_ptr,char ** value_ptr,char * filename,char * line_raw,int line_nr,boolean separator_required)1774 static boolean getTokenValueFromSetupLineExt(char *line,
1775 					     char **token_ptr, char **value_ptr,
1776 					     char *filename, char *line_raw,
1777 					     int line_nr,
1778 					     boolean separator_required)
1779 {
1780   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1781   char *token, *value, *line_ptr;
1782 
1783   /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1784   if (line_raw == NULL)
1785   {
1786     strncpy(line_copy, line, MAX_LINE_LEN);
1787     line_copy[MAX_LINE_LEN] = '\0';
1788     line = line_copy;
1789 
1790     strcpy(line_raw_copy, line_copy);
1791     line_raw = line_raw_copy;
1792   }
1793 
1794   /* cut trailing comment from input line */
1795   for (line_ptr = line; *line_ptr; line_ptr++)
1796   {
1797     if (*line_ptr == '#')
1798     {
1799       *line_ptr = '\0';
1800       break;
1801     }
1802   }
1803 
1804   /* cut trailing whitespaces from input line */
1805   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1806     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1807       *line_ptr = '\0';
1808 
1809   /* ignore empty lines */
1810   if (*line == '\0')
1811     return FALSE;
1812 
1813   /* cut leading whitespaces from token */
1814   for (token = line; *token; token++)
1815     if (*token != ' ' && *token != '\t')
1816       break;
1817 
1818   /* start with empty value as reliable default */
1819   value = "";
1820 
1821   token_value_separator_found = FALSE;
1822 
1823   /* find end of token to determine start of value */
1824   for (line_ptr = token; *line_ptr; line_ptr++)
1825   {
1826 #if 1
1827     /* first look for an explicit token/value separator, like ':' or '=' */
1828     if (*line_ptr == ':' || *line_ptr == '=')
1829 #else
1830     if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1831 #endif
1832     {
1833       *line_ptr = '\0';			/* terminate token string */
1834       value = line_ptr + 1;		/* set beginning of value */
1835 
1836       token_value_separator_found = TRUE;
1837 
1838       break;
1839     }
1840   }
1841 
1842 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1843   /* fallback: if no token/value separator found, also allow whitespaces */
1844   if (!token_value_separator_found && !separator_required)
1845   {
1846     for (line_ptr = token; *line_ptr; line_ptr++)
1847     {
1848       if (*line_ptr == ' ' || *line_ptr == '\t')
1849       {
1850 	*line_ptr = '\0';		/* terminate token string */
1851 	value = line_ptr + 1;		/* set beginning of value */
1852 
1853 	token_value_separator_found = TRUE;
1854 
1855 	break;
1856       }
1857     }
1858 
1859 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1860     if (token_value_separator_found)
1861     {
1862       if (!token_value_separator_warning)
1863       {
1864 	Error(ERR_INFO_LINE, "-");
1865 
1866 	if (filename != NULL)
1867 	{
1868 	  Error(ERR_WARN, "missing token/value separator(s) in config file:");
1869 	  Error(ERR_INFO, "- config file: '%s'", filename);
1870 	}
1871 	else
1872 	{
1873 	  Error(ERR_WARN, "missing token/value separator(s):");
1874 	}
1875 
1876 	token_value_separator_warning = TRUE;
1877       }
1878 
1879       if (filename != NULL)
1880 	Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1881       else
1882 	Error(ERR_INFO, "- line: '%s'", line_raw);
1883     }
1884 #endif
1885   }
1886 #endif
1887 
1888   /* cut trailing whitespaces from token */
1889   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1890     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1891       *line_ptr = '\0';
1892 
1893   /* cut leading whitespaces from value */
1894   for (; *value; value++)
1895     if (*value != ' ' && *value != '\t')
1896       break;
1897 
1898 #if 0
1899   if (*value == '\0')
1900     value = "true";	/* treat tokens without value as "true" */
1901 #endif
1902 
1903   *token_ptr = token;
1904   *value_ptr = value;
1905 
1906   return TRUE;
1907 }
1908 
getTokenValueFromSetupLine(char * line,char ** token,char ** value)1909 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1910 {
1911   /* while the internal (old) interface does not require a token/value
1912      separator (for downwards compatibility with existing files which
1913      don't use them), it is mandatory for the external (new) interface */
1914 
1915   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1916 }
1917 
1918 #if 1
loadSetupFileData(void * setup_file_data,char * filename,boolean top_recursion_level,boolean is_hash)1919 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1920 				 boolean top_recursion_level, boolean is_hash)
1921 {
1922   static SetupFileHash *include_filename_hash = NULL;
1923   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1924   char *token, *value, *line_ptr;
1925   void *insert_ptr = NULL;
1926   boolean read_continued_line = FALSE;
1927   FILE *file;
1928   int line_nr = 0, token_count = 0, include_count = 0;
1929 
1930 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1931   token_value_separator_warning = FALSE;
1932 #endif
1933 
1934 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1935   token_already_exists_warning = FALSE;
1936 #endif
1937 
1938   if (!(file = fopen(filename, MODE_READ)))
1939   {
1940     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1941 
1942     return FALSE;
1943   }
1944 
1945   /* use "insert pointer" to store list end for constant insertion complexity */
1946   if (!is_hash)
1947     insert_ptr = setup_file_data;
1948 
1949   /* on top invocation, create hash to mark included files (to prevent loops) */
1950   if (top_recursion_level)
1951     include_filename_hash = newSetupFileHash();
1952 
1953   /* mark this file as already included (to prevent including it again) */
1954   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1955 
1956   while (!feof(file))
1957   {
1958     /* read next line of input file */
1959     if (!fgets(line, MAX_LINE_LEN, file))
1960       break;
1961 
1962     /* check if line was completely read and is terminated by line break */
1963     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1964       line_nr++;
1965 
1966     /* cut trailing line break (this can be newline and/or carriage return) */
1967     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1968       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1969 	*line_ptr = '\0';
1970 
1971     /* copy raw input line for later use (mainly debugging output) */
1972     strcpy(line_raw, line);
1973 
1974     if (read_continued_line)
1975     {
1976 #if 0
1977       /* !!! ??? WHY ??? !!! */
1978       /* cut leading whitespaces from input line */
1979       for (line_ptr = line; *line_ptr; line_ptr++)
1980 	if (*line_ptr != ' ' && *line_ptr != '\t')
1981 	  break;
1982 #endif
1983 
1984       /* append new line to existing line, if there is enough space */
1985       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1986 	strcat(previous_line, line_ptr);
1987 
1988       strcpy(line, previous_line);	/* copy storage buffer to line */
1989 
1990       read_continued_line = FALSE;
1991     }
1992 
1993     /* if the last character is '\', continue at next line */
1994     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1995     {
1996       line[strlen(line) - 1] = '\0';	/* cut off trailing backslash */
1997       strcpy(previous_line, line);	/* copy line to storage buffer */
1998 
1999       read_continued_line = TRUE;
2000 
2001       continue;
2002     }
2003 
2004     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2005 				       line_raw, line_nr, FALSE))
2006       continue;
2007 
2008     if (*token)
2009     {
2010       if (strEqual(token, "include"))
2011       {
2012 	if (getHashEntry(include_filename_hash, value) == NULL)
2013 	{
2014 	  char *basepath = getBasePath(filename);
2015 	  char *basename = getBaseName(value);
2016 	  char *filename_include = getPath2(basepath, basename);
2017 
2018 #if 0
2019 	  Error(ERR_INFO, "[including file '%s']", filename_include);
2020 #endif
2021 
2022 	  loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2023 
2024 	  free(basepath);
2025 	  free(basename);
2026 	  free(filename_include);
2027 
2028 	  include_count++;
2029 	}
2030 	else
2031 	{
2032 	  Error(ERR_WARN, "ignoring already processed file '%s'", value);
2033 	}
2034       }
2035       else
2036       {
2037 	if (is_hash)
2038 	{
2039 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2040 	  char *old_value =
2041 	    getHashEntry((SetupFileHash *)setup_file_data, token);
2042 
2043 	  if (old_value != NULL)
2044 	  {
2045 	    if (!token_already_exists_warning)
2046 	    {
2047 	      Error(ERR_INFO_LINE, "-");
2048 	      Error(ERR_WARN, "duplicate token(s) found in config file:");
2049 	      Error(ERR_INFO, "- config file: '%s'", filename);
2050 
2051 	      token_already_exists_warning = TRUE;
2052 	    }
2053 
2054 	    Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2055 	    Error(ERR_INFO, "  old value: '%s'", old_value);
2056 	    Error(ERR_INFO, "  new value: '%s'", value);
2057 	  }
2058 #endif
2059 
2060 	  setHashEntry((SetupFileHash *)setup_file_data, token, value);
2061 	}
2062 	else
2063 	{
2064 	  insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2065 	}
2066 
2067 	token_count++;
2068       }
2069     }
2070   }
2071 
2072   fclose(file);
2073 
2074 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2075   if (token_value_separator_warning)
2076     Error(ERR_INFO_LINE, "-");
2077 #endif
2078 
2079 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2080   if (token_already_exists_warning)
2081     Error(ERR_INFO_LINE, "-");
2082 #endif
2083 
2084   if (token_count == 0 && include_count == 0)
2085     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2086 
2087   if (top_recursion_level)
2088     freeSetupFileHash(include_filename_hash);
2089 
2090   return TRUE;
2091 }
2092 
2093 #else
2094 
loadSetupFileData(void * setup_file_data,char * filename,boolean top_recursion_level,boolean is_hash)2095 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2096 				 boolean top_recursion_level, boolean is_hash)
2097 {
2098   static SetupFileHash *include_filename_hash = NULL;
2099   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2100   char *token, *value, *line_ptr;
2101   void *insert_ptr = NULL;
2102   boolean read_continued_line = FALSE;
2103   FILE *file;
2104   int line_nr = 0;
2105   int token_count = 0;
2106 
2107 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2108   token_value_separator_warning = FALSE;
2109 #endif
2110 
2111   if (!(file = fopen(filename, MODE_READ)))
2112   {
2113     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2114 
2115     return FALSE;
2116   }
2117 
2118   /* use "insert pointer" to store list end for constant insertion complexity */
2119   if (!is_hash)
2120     insert_ptr = setup_file_data;
2121 
2122   /* on top invocation, create hash to mark included files (to prevent loops) */
2123   if (top_recursion_level)
2124     include_filename_hash = newSetupFileHash();
2125 
2126   /* mark this file as already included (to prevent including it again) */
2127   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2128 
2129   while (!feof(file))
2130   {
2131     /* read next line of input file */
2132     if (!fgets(line, MAX_LINE_LEN, file))
2133       break;
2134 
2135     /* check if line was completely read and is terminated by line break */
2136     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2137       line_nr++;
2138 
2139     /* cut trailing line break (this can be newline and/or carriage return) */
2140     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2141       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2142 	*line_ptr = '\0';
2143 
2144     /* copy raw input line for later use (mainly debugging output) */
2145     strcpy(line_raw, line);
2146 
2147     if (read_continued_line)
2148     {
2149       /* cut leading whitespaces from input line */
2150       for (line_ptr = line; *line_ptr; line_ptr++)
2151 	if (*line_ptr != ' ' && *line_ptr != '\t')
2152 	  break;
2153 
2154       /* append new line to existing line, if there is enough space */
2155       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2156 	strcat(previous_line, line_ptr);
2157 
2158       strcpy(line, previous_line);	/* copy storage buffer to line */
2159 
2160       read_continued_line = FALSE;
2161     }
2162 
2163     /* if the last character is '\', continue at next line */
2164     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2165     {
2166       line[strlen(line) - 1] = '\0';	/* cut off trailing backslash */
2167       strcpy(previous_line, line);	/* copy line to storage buffer */
2168 
2169       read_continued_line = TRUE;
2170 
2171       continue;
2172     }
2173 
2174     /* cut trailing comment from input line */
2175     for (line_ptr = line; *line_ptr; line_ptr++)
2176     {
2177       if (*line_ptr == '#')
2178       {
2179 	*line_ptr = '\0';
2180 	break;
2181       }
2182     }
2183 
2184     /* cut trailing whitespaces from input line */
2185     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2186       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2187 	*line_ptr = '\0';
2188 
2189     /* ignore empty lines */
2190     if (*line == '\0')
2191       continue;
2192 
2193     /* cut leading whitespaces from token */
2194     for (token = line; *token; token++)
2195       if (*token != ' ' && *token != '\t')
2196 	break;
2197 
2198     /* start with empty value as reliable default */
2199     value = "";
2200 
2201     token_value_separator_found = FALSE;
2202 
2203     /* find end of token to determine start of value */
2204     for (line_ptr = token; *line_ptr; line_ptr++)
2205     {
2206 #if 1
2207       /* first look for an explicit token/value separator, like ':' or '=' */
2208       if (*line_ptr == ':' || *line_ptr == '=')
2209 #else
2210       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2211 #endif
2212       {
2213 	*line_ptr = '\0';		/* terminate token string */
2214 	value = line_ptr + 1;		/* set beginning of value */
2215 
2216 	token_value_separator_found = TRUE;
2217 
2218 	break;
2219       }
2220     }
2221 
2222 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2223     /* fallback: if no token/value separator found, also allow whitespaces */
2224     if (!token_value_separator_found)
2225     {
2226       for (line_ptr = token; *line_ptr; line_ptr++)
2227       {
2228 	if (*line_ptr == ' ' || *line_ptr == '\t')
2229 	{
2230 	  *line_ptr = '\0';		/* terminate token string */
2231 	  value = line_ptr + 1;		/* set beginning of value */
2232 
2233 	  token_value_separator_found = TRUE;
2234 
2235 	  break;
2236 	}
2237       }
2238 
2239 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2240       if (token_value_separator_found)
2241       {
2242 	if (!token_value_separator_warning)
2243 	{
2244 	  Error(ERR_INFO_LINE, "-");
2245 	  Error(ERR_WARN, "missing token/value separator(s) in config file:");
2246 	  Error(ERR_INFO, "- config file: '%s'", filename);
2247 
2248 	  token_value_separator_warning = TRUE;
2249 	}
2250 
2251 	Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2252       }
2253 #endif
2254     }
2255 #endif
2256 
2257     /* cut trailing whitespaces from token */
2258     for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2259       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2260 	*line_ptr = '\0';
2261 
2262     /* cut leading whitespaces from value */
2263     for (; *value; value++)
2264       if (*value != ' ' && *value != '\t')
2265 	break;
2266 
2267 #if 0
2268     if (*value == '\0')
2269       value = "true";	/* treat tokens without value as "true" */
2270 #endif
2271 
2272     if (*token)
2273     {
2274       if (strEqual(token, "include"))
2275       {
2276 	if (getHashEntry(include_filename_hash, value) == NULL)
2277 	{
2278 	  char *basepath = getBasePath(filename);
2279 	  char *basename = getBaseName(value);
2280 	  char *filename_include = getPath2(basepath, basename);
2281 
2282 #if 0
2283 	  Error(ERR_INFO, "[including file '%s']", filename_include);
2284 #endif
2285 
2286 	  loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2287 
2288 	  free(basepath);
2289 	  free(basename);
2290 	  free(filename_include);
2291 	}
2292 	else
2293 	{
2294 	  Error(ERR_WARN, "ignoring already processed file '%s'", value);
2295 	}
2296       }
2297       else
2298       {
2299 	if (is_hash)
2300 	  setHashEntry((SetupFileHash *)setup_file_data, token, value);
2301 	else
2302 	  insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2303 
2304 	token_count++;
2305       }
2306     }
2307   }
2308 
2309   fclose(file);
2310 
2311 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2312   if (token_value_separator_warning)
2313     Error(ERR_INFO_LINE, "-");
2314 #endif
2315 
2316   if (token_count == 0)
2317     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2318 
2319   if (top_recursion_level)
2320     freeSetupFileHash(include_filename_hash);
2321 
2322   return TRUE;
2323 }
2324 #endif
2325 
saveSetupFileHash(SetupFileHash * hash,char * filename)2326 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2327 {
2328   FILE *file;
2329 
2330   if (!(file = fopen(filename, MODE_WRITE)))
2331   {
2332     Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2333 
2334     return;
2335   }
2336 
2337   BEGIN_HASH_ITERATION(hash, itr)
2338   {
2339     fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2340 						 HASH_ITERATION_VALUE(itr)));
2341   }
2342   END_HASH_ITERATION(hash, itr)
2343 
2344   fclose(file);
2345 }
2346 
loadSetupFileList(char * filename)2347 SetupFileList *loadSetupFileList(char *filename)
2348 {
2349   SetupFileList *setup_file_list = newSetupFileList("", "");
2350   SetupFileList *first_valid_list_entry;
2351 
2352   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2353   {
2354     freeSetupFileList(setup_file_list);
2355 
2356     return NULL;
2357   }
2358 
2359   first_valid_list_entry = setup_file_list->next;
2360 
2361   /* free empty list header */
2362   setup_file_list->next = NULL;
2363   freeSetupFileList(setup_file_list);
2364 
2365   return first_valid_list_entry;
2366 }
2367 
loadSetupFileHash(char * filename)2368 SetupFileHash *loadSetupFileHash(char *filename)
2369 {
2370   SetupFileHash *setup_file_hash = newSetupFileHash();
2371 
2372   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2373   {
2374     freeSetupFileHash(setup_file_hash);
2375 
2376     return NULL;
2377   }
2378 
2379   return setup_file_hash;
2380 }
2381 
checkSetupFileHashIdentifier(SetupFileHash * setup_file_hash,char * filename,char * identifier)2382 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2383 				  char *filename, char *identifier)
2384 {
2385   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2386 
2387   if (value == NULL)
2388     Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2389   else if (!checkCookieString(value, identifier))
2390     Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2391 }
2392 
2393 
2394 /* ========================================================================= */
2395 /* setup file stuff                                                          */
2396 /* ========================================================================= */
2397 
2398 #define TOKEN_STR_LAST_LEVEL_SERIES		"last_level_series"
2399 #define TOKEN_STR_LAST_PLAYED_LEVEL		"last_played_level"
2400 #define TOKEN_STR_HANDICAP_LEVEL		"handicap_level"
2401 
2402 /* level directory info */
2403 #define LEVELINFO_TOKEN_IDENTIFIER		0
2404 #define LEVELINFO_TOKEN_NAME			1
2405 #define LEVELINFO_TOKEN_NAME_SORTING		2
2406 #define LEVELINFO_TOKEN_AUTHOR			3
2407 #define LEVELINFO_TOKEN_YEAR			4
2408 #define LEVELINFO_TOKEN_IMPORTED_FROM		5
2409 #define LEVELINFO_TOKEN_IMPORTED_BY		6
2410 #define LEVELINFO_TOKEN_TESTED_BY		7
2411 #define LEVELINFO_TOKEN_LEVELS			8
2412 #define LEVELINFO_TOKEN_FIRST_LEVEL		9
2413 #define LEVELINFO_TOKEN_SORT_PRIORITY		10
2414 #define LEVELINFO_TOKEN_LATEST_ENGINE		11
2415 #define LEVELINFO_TOKEN_LEVEL_GROUP		12
2416 #define LEVELINFO_TOKEN_READONLY		13
2417 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS	14
2418 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA	15
2419 #define LEVELINFO_TOKEN_GRAPHICS_SET		16
2420 #define LEVELINFO_TOKEN_SOUNDS_SET		17
2421 #define LEVELINFO_TOKEN_MUSIC_SET		18
2422 #define LEVELINFO_TOKEN_FILENAME		19
2423 #define LEVELINFO_TOKEN_FILETYPE		20
2424 #define LEVELINFO_TOKEN_SPECIAL_FLAGS		21
2425 #define LEVELINFO_TOKEN_HANDICAP		22
2426 #define LEVELINFO_TOKEN_SKIP_LEVELS		23
2427 
2428 #define NUM_LEVELINFO_TOKENS			24
2429 
2430 static LevelDirTree ldi;
2431 
2432 static struct TokenInfo levelinfo_tokens[] =
2433 {
2434   /* level directory info */
2435   { TYPE_STRING,	&ldi.identifier,	"identifier"		},
2436   { TYPE_STRING,	&ldi.name,		"name"			},
2437   { TYPE_STRING,	&ldi.name_sorting,	"name_sorting"		},
2438   { TYPE_STRING,	&ldi.author,		"author"		},
2439   { TYPE_STRING,	&ldi.year,		"year"			},
2440   { TYPE_STRING,	&ldi.imported_from,	"imported_from"		},
2441   { TYPE_STRING,	&ldi.imported_by,	"imported_by"		},
2442   { TYPE_STRING,	&ldi.tested_by,		"tested_by"		},
2443   { TYPE_INTEGER,	&ldi.levels,		"levels"		},
2444   { TYPE_INTEGER,	&ldi.first_level,	"first_level"		},
2445   { TYPE_INTEGER,	&ldi.sort_priority,	"sort_priority"		},
2446   { TYPE_BOOLEAN,	&ldi.latest_engine,	"latest_engine"		},
2447   { TYPE_BOOLEAN,	&ldi.level_group,	"level_group"		},
2448   { TYPE_BOOLEAN,	&ldi.readonly,		"readonly"		},
2449   { TYPE_STRING,	&ldi.graphics_set_ecs,	"graphics_set.ecs"	},
2450   { TYPE_STRING,	&ldi.graphics_set_aga,	"graphics_set.aga"	},
2451   { TYPE_STRING,	&ldi.graphics_set,	"graphics_set"		},
2452   { TYPE_STRING,	&ldi.sounds_set,	"sounds_set"		},
2453   { TYPE_STRING,	&ldi.music_set,		"music_set"		},
2454   { TYPE_STRING,	&ldi.level_filename,	"filename"		},
2455   { TYPE_STRING,	&ldi.level_filetype,	"filetype"		},
2456   { TYPE_STRING,	&ldi.special_flags,	"special_flags"		},
2457   { TYPE_BOOLEAN,	&ldi.handicap,		"handicap"		},
2458   { TYPE_BOOLEAN,	&ldi.skip_levels,	"skip_levels"		}
2459 };
2460 
2461 static struct TokenInfo artworkinfo_tokens[] =
2462 {
2463   /* artwork directory info */
2464   { TYPE_STRING,	&ldi.identifier,	"identifier"		},
2465   { TYPE_STRING,	&ldi.subdir,		"subdir"		},
2466   { TYPE_STRING,	&ldi.name,		"name"			},
2467   { TYPE_STRING,	&ldi.name_sorting,	"name_sorting"		},
2468   { TYPE_STRING,	&ldi.author,		"author"		},
2469   { TYPE_INTEGER,	&ldi.sort_priority,	"sort_priority"		},
2470   { TYPE_STRING,	&ldi.basepath,		"basepath"		},
2471   { TYPE_STRING,	&ldi.fullpath,		"fullpath"		},
2472   { TYPE_BOOLEAN,	&ldi.in_user_dir,	"in_user_dir"		},
2473   { TYPE_INTEGER,	&ldi.color,		"color"			},
2474   { TYPE_STRING,	&ldi.class_desc,	"class_desc"		},
2475 
2476   { -1,			NULL,			NULL			},
2477 };
2478 
setTreeInfoToDefaults(TreeInfo * ti,int type)2479 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2480 {
2481   ti->type = type;
2482 
2483   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2484 		  ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2485 		  ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2486 		  ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2487 		  NULL);
2488 
2489   ti->node_parent = NULL;
2490   ti->node_group = NULL;
2491   ti->next = NULL;
2492 
2493   ti->cl_first = -1;
2494   ti->cl_cursor = -1;
2495 
2496   ti->subdir = NULL;
2497   ti->fullpath = NULL;
2498   ti->basepath = NULL;
2499   ti->identifier = NULL;
2500   ti->name = getStringCopy(ANONYMOUS_NAME);
2501   ti->name_sorting = NULL;
2502   ti->author = getStringCopy(ANONYMOUS_NAME);
2503   ti->year = NULL;
2504 
2505   ti->sort_priority = LEVELCLASS_UNDEFINED;	/* default: least priority */
2506   ti->latest_engine = FALSE;			/* default: get from level */
2507   ti->parent_link = FALSE;
2508   ti->in_user_dir = FALSE;
2509   ti->user_defined = FALSE;
2510   ti->color = 0;
2511   ti->class_desc = NULL;
2512 
2513   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2514 
2515   if (ti->type == TREE_TYPE_LEVEL_DIR)
2516   {
2517     ti->imported_from = NULL;
2518     ti->imported_by = NULL;
2519     ti->tested_by = NULL;
2520 
2521     ti->graphics_set_ecs = NULL;
2522     ti->graphics_set_aga = NULL;
2523     ti->graphics_set = NULL;
2524     ti->sounds_set = NULL;
2525     ti->music_set = NULL;
2526     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2527     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2528     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2529 
2530     ti->level_filename = NULL;
2531     ti->level_filetype = NULL;
2532 
2533     ti->special_flags = NULL;
2534 
2535     ti->levels = 0;
2536     ti->first_level = 0;
2537     ti->last_level = 0;
2538     ti->level_group = FALSE;
2539     ti->handicap_level = 0;
2540     ti->readonly = TRUE;
2541     ti->handicap = TRUE;
2542     ti->skip_levels = FALSE;
2543   }
2544 }
2545 
setTreeInfoToDefaultsFromParent(TreeInfo * ti,TreeInfo * parent)2546 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2547 {
2548   if (parent == NULL)
2549   {
2550     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2551 
2552     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2553 
2554     return;
2555   }
2556 
2557   /* copy all values from the parent structure */
2558 
2559   ti->type = parent->type;
2560 
2561   ti->node_top = parent->node_top;
2562   ti->node_parent = parent;
2563   ti->node_group = NULL;
2564   ti->next = NULL;
2565 
2566   ti->cl_first = -1;
2567   ti->cl_cursor = -1;
2568 
2569   ti->subdir = NULL;
2570   ti->fullpath = NULL;
2571   ti->basepath = NULL;
2572   ti->identifier = NULL;
2573   ti->name = getStringCopy(ANONYMOUS_NAME);
2574   ti->name_sorting = NULL;
2575   ti->author = getStringCopy(parent->author);
2576   ti->year = getStringCopy(parent->year);
2577 
2578   ti->sort_priority = parent->sort_priority;
2579   ti->latest_engine = parent->latest_engine;
2580   ti->parent_link = FALSE;
2581   ti->in_user_dir = parent->in_user_dir;
2582   ti->user_defined = parent->user_defined;
2583   ti->color = parent->color;
2584   ti->class_desc = getStringCopy(parent->class_desc);
2585 
2586   ti->infotext = getStringCopy(parent->infotext);
2587 
2588   if (ti->type == TREE_TYPE_LEVEL_DIR)
2589   {
2590     ti->imported_from = getStringCopy(parent->imported_from);
2591     ti->imported_by = getStringCopy(parent->imported_by);
2592     ti->tested_by = getStringCopy(parent->tested_by);
2593 
2594     ti->graphics_set_ecs = NULL;
2595     ti->graphics_set_aga = NULL;
2596     ti->graphics_set = NULL;
2597     ti->sounds_set = NULL;
2598     ti->music_set = NULL;
2599     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2600     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2601     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2602 
2603     ti->level_filename = NULL;
2604     ti->level_filetype = NULL;
2605 
2606     ti->special_flags = getStringCopy(parent->special_flags);
2607 
2608     ti->levels = 0;
2609     ti->first_level = 0;
2610     ti->last_level = 0;
2611     ti->level_group = FALSE;
2612     ti->handicap_level = 0;
2613 #if 1
2614     ti->readonly = parent->readonly;
2615 #else
2616     ti->readonly = TRUE;
2617 #endif
2618     ti->handicap = TRUE;
2619     ti->skip_levels = FALSE;
2620   }
2621 }
2622 
getTreeInfoCopy(TreeInfo * ti)2623 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2624 {
2625   TreeInfo *ti_copy = newTreeInfo();
2626 
2627   /* copy all values from the original structure */
2628 
2629   ti_copy->type			= ti->type;
2630 
2631   ti_copy->node_top		= ti->node_top;
2632   ti_copy->node_parent		= ti->node_parent;
2633   ti_copy->node_group		= ti->node_group;
2634   ti_copy->next			= ti->next;
2635 
2636   ti_copy->cl_first		= ti->cl_first;
2637   ti_copy->cl_cursor		= ti->cl_cursor;
2638 
2639   ti_copy->subdir		= getStringCopy(ti->subdir);
2640   ti_copy->fullpath		= getStringCopy(ti->fullpath);
2641   ti_copy->basepath		= getStringCopy(ti->basepath);
2642   ti_copy->identifier		= getStringCopy(ti->identifier);
2643   ti_copy->name			= getStringCopy(ti->name);
2644   ti_copy->name_sorting		= getStringCopy(ti->name_sorting);
2645   ti_copy->author		= getStringCopy(ti->author);
2646   ti_copy->year			= getStringCopy(ti->year);
2647   ti_copy->imported_from	= getStringCopy(ti->imported_from);
2648   ti_copy->imported_by		= getStringCopy(ti->imported_by);
2649   ti_copy->tested_by		= getStringCopy(ti->tested_by);
2650 
2651   ti_copy->graphics_set_ecs	= getStringCopy(ti->graphics_set_ecs);
2652   ti_copy->graphics_set_aga	= getStringCopy(ti->graphics_set_aga);
2653   ti_copy->graphics_set		= getStringCopy(ti->graphics_set);
2654   ti_copy->sounds_set		= getStringCopy(ti->sounds_set);
2655   ti_copy->music_set		= getStringCopy(ti->music_set);
2656   ti_copy->graphics_path	= getStringCopy(ti->graphics_path);
2657   ti_copy->sounds_path		= getStringCopy(ti->sounds_path);
2658   ti_copy->music_path		= getStringCopy(ti->music_path);
2659 
2660   ti_copy->level_filename	= getStringCopy(ti->level_filename);
2661   ti_copy->level_filetype	= getStringCopy(ti->level_filetype);
2662 
2663   ti_copy->special_flags	= getStringCopy(ti->special_flags);
2664 
2665   ti_copy->levels		= ti->levels;
2666   ti_copy->first_level		= ti->first_level;
2667   ti_copy->last_level		= ti->last_level;
2668   ti_copy->sort_priority	= ti->sort_priority;
2669 
2670   ti_copy->latest_engine	= ti->latest_engine;
2671 
2672   ti_copy->level_group		= ti->level_group;
2673   ti_copy->parent_link		= ti->parent_link;
2674   ti_copy->in_user_dir		= ti->in_user_dir;
2675   ti_copy->user_defined		= ti->user_defined;
2676   ti_copy->readonly		= ti->readonly;
2677   ti_copy->handicap		= ti->handicap;
2678   ti_copy->skip_levels		= ti->skip_levels;
2679 
2680   ti_copy->color		= ti->color;
2681   ti_copy->class_desc		= getStringCopy(ti->class_desc);
2682   ti_copy->handicap_level	= ti->handicap_level;
2683 
2684   ti_copy->infotext		= getStringCopy(ti->infotext);
2685 
2686   return ti_copy;
2687 }
2688 
freeTreeInfo(TreeInfo * ti)2689 void freeTreeInfo(TreeInfo *ti)
2690 {
2691   if (ti == NULL)
2692     return;
2693 
2694   checked_free(ti->subdir);
2695   checked_free(ti->fullpath);
2696   checked_free(ti->basepath);
2697   checked_free(ti->identifier);
2698 
2699   checked_free(ti->name);
2700   checked_free(ti->name_sorting);
2701   checked_free(ti->author);
2702   checked_free(ti->year);
2703 
2704   checked_free(ti->class_desc);
2705 
2706   checked_free(ti->infotext);
2707 
2708   if (ti->type == TREE_TYPE_LEVEL_DIR)
2709   {
2710     checked_free(ti->imported_from);
2711     checked_free(ti->imported_by);
2712     checked_free(ti->tested_by);
2713 
2714     checked_free(ti->graphics_set_ecs);
2715     checked_free(ti->graphics_set_aga);
2716     checked_free(ti->graphics_set);
2717     checked_free(ti->sounds_set);
2718     checked_free(ti->music_set);
2719 
2720     checked_free(ti->graphics_path);
2721     checked_free(ti->sounds_path);
2722     checked_free(ti->music_path);
2723 
2724     checked_free(ti->level_filename);
2725     checked_free(ti->level_filetype);
2726 
2727     checked_free(ti->special_flags);
2728   }
2729 
2730   checked_free(ti);
2731 }
2732 
setSetupInfo(struct TokenInfo * token_info,int token_nr,char * token_value)2733 void setSetupInfo(struct TokenInfo *token_info,
2734 		  int token_nr, char *token_value)
2735 {
2736   int token_type = token_info[token_nr].type;
2737   void *setup_value = token_info[token_nr].value;
2738 
2739   if (token_value == NULL)
2740     return;
2741 
2742   /* set setup field to corresponding token value */
2743   switch (token_type)
2744   {
2745     case TYPE_BOOLEAN:
2746     case TYPE_SWITCH:
2747       *(boolean *)setup_value = get_boolean_from_string(token_value);
2748       break;
2749 
2750     case TYPE_SWITCH3:
2751       *(int *)setup_value = get_switch3_from_string(token_value);
2752       break;
2753 
2754     case TYPE_KEY:
2755       *(Key *)setup_value = getKeyFromKeyName(token_value);
2756       break;
2757 
2758     case TYPE_KEY_X11:
2759       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2760       break;
2761 
2762     case TYPE_INTEGER:
2763       *(int *)setup_value = get_integer_from_string(token_value);
2764       break;
2765 
2766     case TYPE_STRING:
2767       checked_free(*(char **)setup_value);
2768       *(char **)setup_value = getStringCopy(token_value);
2769       break;
2770 
2771     default:
2772       break;
2773   }
2774 }
2775 
compareTreeInfoEntries(const void * object1,const void * object2)2776 static int compareTreeInfoEntries(const void *object1, const void *object2)
2777 {
2778   const TreeInfo *entry1 = *((TreeInfo **)object1);
2779   const TreeInfo *entry2 = *((TreeInfo **)object2);
2780   int class_sorting1 = 0, class_sorting2 = 0;
2781   int compare_result;
2782 
2783   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2784   {
2785     class_sorting1 = LEVELSORTING(entry1);
2786     class_sorting2 = LEVELSORTING(entry2);
2787   }
2788   else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2789 	   entry1->type == TREE_TYPE_SOUNDS_DIR ||
2790 	   entry1->type == TREE_TYPE_MUSIC_DIR)
2791   {
2792     class_sorting1 = ARTWORKSORTING(entry1);
2793     class_sorting2 = ARTWORKSORTING(entry2);
2794   }
2795 
2796   if (entry1->parent_link || entry2->parent_link)
2797     compare_result = (entry1->parent_link ? -1 : +1);
2798   else if (entry1->sort_priority == entry2->sort_priority)
2799   {
2800     char *name1 = getStringToLower(entry1->name_sorting);
2801     char *name2 = getStringToLower(entry2->name_sorting);
2802 
2803     compare_result = strcmp(name1, name2);
2804 
2805     free(name1);
2806     free(name2);
2807   }
2808   else if (class_sorting1 == class_sorting2)
2809     compare_result = entry1->sort_priority - entry2->sort_priority;
2810   else
2811     compare_result = class_sorting1 - class_sorting2;
2812 
2813   return compare_result;
2814 }
2815 
createParentTreeInfoNode(TreeInfo * node_parent)2816 static void createParentTreeInfoNode(TreeInfo *node_parent)
2817 {
2818   TreeInfo *ti_new;
2819 
2820   if (node_parent == NULL)
2821     return;
2822 
2823   ti_new = newTreeInfo();
2824   setTreeInfoToDefaults(ti_new, node_parent->type);
2825 
2826   ti_new->node_parent = node_parent;
2827   ti_new->parent_link = TRUE;
2828 
2829   setString(&ti_new->identifier, node_parent->identifier);
2830   setString(&ti_new->name, ".. (parent directory)");
2831   setString(&ti_new->name_sorting, ti_new->name);
2832 
2833   setString(&ti_new->subdir, "..");
2834   setString(&ti_new->fullpath, node_parent->fullpath);
2835 
2836   ti_new->sort_priority = node_parent->sort_priority;
2837   ti_new->latest_engine = node_parent->latest_engine;
2838 
2839   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2840 
2841   pushTreeInfo(&node_parent->node_group, ti_new);
2842 }
2843 
2844 
2845 /* -------------------------------------------------------------------------- */
2846 /* functions for handling level and custom artwork info cache                 */
2847 /* -------------------------------------------------------------------------- */
2848 
LoadArtworkInfoCache()2849 static void LoadArtworkInfoCache()
2850 {
2851   InitCacheDirectory();
2852 
2853   if (artworkinfo_cache_old == NULL)
2854   {
2855     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2856 
2857     /* try to load artwork info hash from already existing cache file */
2858     artworkinfo_cache_old = loadSetupFileHash(filename);
2859 
2860     /* if no artwork info cache file was found, start with empty hash */
2861     if (artworkinfo_cache_old == NULL)
2862       artworkinfo_cache_old = newSetupFileHash();
2863 
2864     free(filename);
2865   }
2866 
2867   if (artworkinfo_cache_new == NULL)
2868     artworkinfo_cache_new = newSetupFileHash();
2869 }
2870 
SaveArtworkInfoCache()2871 static void SaveArtworkInfoCache()
2872 {
2873   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2874 
2875   InitCacheDirectory();
2876 
2877   saveSetupFileHash(artworkinfo_cache_new, filename);
2878 
2879   free(filename);
2880 }
2881 
getCacheTokenPrefix(char * prefix1,char * prefix2)2882 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2883 {
2884   static char *prefix = NULL;
2885 
2886   checked_free(prefix);
2887 
2888   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2889 
2890   return prefix;
2891 }
2892 
2893 /* (identical to above function, but separate string buffer needed -- nasty) */
getCacheToken(char * prefix,char * suffix)2894 static char *getCacheToken(char *prefix, char *suffix)
2895 {
2896   static char *token = NULL;
2897 
2898   checked_free(token);
2899 
2900   token = getStringCat2WithSeparator(prefix, suffix, ".");
2901 
2902   return token;
2903 }
2904 
getFileTimestampString(char * filename)2905 static char *getFileTimestampString(char *filename)
2906 {
2907 #if 1
2908   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2909 #else
2910   struct stat file_status;
2911 
2912   if (stat(filename, &file_status) != 0)	/* cannot stat file */
2913     return getStringCopy(i_to_a(0));
2914 
2915   return getStringCopy(i_to_a(file_status.st_mtime));
2916 #endif
2917 }
2918 
modifiedFileTimestamp(char * filename,char * timestamp_string)2919 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2920 {
2921   struct stat file_status;
2922 
2923   if (timestamp_string == NULL)
2924     return TRUE;
2925 
2926   if (stat(filename, &file_status) != 0)	/* cannot stat file */
2927     return TRUE;
2928 
2929   return (file_status.st_mtime != atoi(timestamp_string));
2930 }
2931 
getArtworkInfoCacheEntry(LevelDirTree * level_node,int type)2932 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2933 {
2934   char *identifier = level_node->subdir;
2935   char *type_string = ARTWORK_DIRECTORY(type);
2936   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2937   char *token_main = getCacheToken(token_prefix, "CACHED");
2938   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2939   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2940   TreeInfo *artwork_info = NULL;
2941 
2942   if (!use_artworkinfo_cache)
2943     return NULL;
2944 
2945   if (cached)
2946   {
2947     int i;
2948 
2949     artwork_info = newTreeInfo();
2950     setTreeInfoToDefaults(artwork_info, type);
2951 
2952     /* set all structure fields according to the token/value pairs */
2953     ldi = *artwork_info;
2954     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2955     {
2956       char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2957       char *value = getHashEntry(artworkinfo_cache_old, token);
2958 
2959       setSetupInfo(artworkinfo_tokens, i, value);
2960 
2961       /* check if cache entry for this item is invalid or incomplete */
2962       if (value == NULL)
2963       {
2964 #if 1
2965 	Error(ERR_WARN, "cache entry '%s' invalid", token);
2966 #endif
2967 
2968 	cached = FALSE;
2969       }
2970     }
2971 
2972     *artwork_info = ldi;
2973   }
2974 
2975   if (cached)
2976   {
2977     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2978 					LEVELINFO_FILENAME);
2979     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2980 					  ARTWORKINFO_FILENAME(type));
2981 
2982     /* check if corresponding "levelinfo.conf" file has changed */
2983     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2984     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2985 
2986     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2987       cached = FALSE;
2988 
2989     /* check if corresponding "<artworkinfo>.conf" file has changed */
2990     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2991     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2992 
2993     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2994       cached = FALSE;
2995 
2996 #if 0
2997     if (!cached)
2998       printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2999 #endif
3000 
3001     checked_free(filename_levelinfo);
3002     checked_free(filename_artworkinfo);
3003   }
3004 
3005   if (!cached && artwork_info != NULL)
3006   {
3007     freeTreeInfo(artwork_info);
3008 
3009     return NULL;
3010   }
3011 
3012   return artwork_info;
3013 }
3014 
setArtworkInfoCacheEntry(TreeInfo * artwork_info,LevelDirTree * level_node,int type)3015 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3016 				     LevelDirTree *level_node, int type)
3017 {
3018   char *identifier = level_node->subdir;
3019   char *type_string = ARTWORK_DIRECTORY(type);
3020   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3021   char *token_main = getCacheToken(token_prefix, "CACHED");
3022   boolean set_cache_timestamps = TRUE;
3023   int i;
3024 
3025   setHashEntry(artworkinfo_cache_new, token_main, "true");
3026 
3027   if (set_cache_timestamps)
3028   {
3029     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3030 					LEVELINFO_FILENAME);
3031     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3032 					  ARTWORKINFO_FILENAME(type));
3033     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3034     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3035 
3036     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3037     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3038 
3039     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3040     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3041 
3042     checked_free(filename_levelinfo);
3043     checked_free(filename_artworkinfo);
3044     checked_free(timestamp_levelinfo);
3045     checked_free(timestamp_artworkinfo);
3046   }
3047 
3048   ldi = *artwork_info;
3049   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3050   {
3051     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3052     char *value = getSetupValue(artworkinfo_tokens[i].type,
3053 				artworkinfo_tokens[i].value);
3054     if (value != NULL)
3055       setHashEntry(artworkinfo_cache_new, token, value);
3056   }
3057 }
3058 
3059 
3060 /* -------------------------------------------------------------------------- */
3061 /* functions for loading level info and custom artwork info                   */
3062 /* -------------------------------------------------------------------------- */
3063 
3064 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3065 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3066 
LoadLevelInfoFromLevelConf(TreeInfo ** node_first,TreeInfo * node_parent,char * level_directory,char * directory_name)3067 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3068 					  TreeInfo *node_parent,
3069 					  char *level_directory,
3070 					  char *directory_name)
3071 {
3072 #if 0
3073   static unsigned int progress_delay = 0;
3074   unsigned int progress_delay_value = 100;	/* (in milliseconds) */
3075 #endif
3076   char *directory_path = getPath2(level_directory, directory_name);
3077   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3078   SetupFileHash *setup_file_hash;
3079   LevelDirTree *leveldir_new = NULL;
3080   int i;
3081 
3082   /* unless debugging, silently ignore directories without "levelinfo.conf" */
3083   if (!options.debug && !fileExists(filename))
3084   {
3085     free(directory_path);
3086     free(filename);
3087 
3088     return FALSE;
3089   }
3090 
3091   setup_file_hash = loadSetupFileHash(filename);
3092 
3093   if (setup_file_hash == NULL)
3094   {
3095     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3096 
3097     free(directory_path);
3098     free(filename);
3099 
3100     return FALSE;
3101   }
3102 
3103   leveldir_new = newTreeInfo();
3104 
3105   if (node_parent)
3106     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3107   else
3108     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3109 
3110   leveldir_new->subdir = getStringCopy(directory_name);
3111 
3112   checkSetupFileHashIdentifier(setup_file_hash, filename,
3113 			       getCookie("LEVELINFO"));
3114 
3115   /* set all structure fields according to the token/value pairs */
3116   ldi = *leveldir_new;
3117   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3118     setSetupInfo(levelinfo_tokens, i,
3119 		 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3120   *leveldir_new = ldi;
3121 
3122   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3123     setString(&leveldir_new->name, leveldir_new->subdir);
3124 
3125   if (leveldir_new->identifier == NULL)
3126     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3127 
3128   if (leveldir_new->name_sorting == NULL)
3129     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3130 
3131   if (node_parent == NULL)		/* top level group */
3132   {
3133     leveldir_new->basepath = getStringCopy(level_directory);
3134     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3135   }
3136   else					/* sub level group */
3137   {
3138     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3139     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3140   }
3141 
3142 #if 0
3143   if (leveldir_new->levels < 1)
3144     leveldir_new->levels = 1;
3145 #endif
3146 
3147   leveldir_new->last_level =
3148     leveldir_new->first_level + leveldir_new->levels - 1;
3149 
3150   leveldir_new->in_user_dir =
3151     (!strEqual(leveldir_new->basepath, options.level_directory));
3152 
3153 #if 0
3154   printf("::: '%s' -> %d\n",
3155 	 leveldir_new->identifier,
3156 	 leveldir_new->in_user_dir);
3157 #endif
3158 
3159   /* adjust some settings if user's private level directory was detected */
3160   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3161       leveldir_new->in_user_dir &&
3162       (strEqual(leveldir_new->subdir, getLoginName()) ||
3163        strEqual(leveldir_new->name,   getLoginName()) ||
3164        strEqual(leveldir_new->author, getRealName())))
3165   {
3166     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3167     leveldir_new->readonly = FALSE;
3168   }
3169 
3170   leveldir_new->user_defined =
3171     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3172 
3173   leveldir_new->color = LEVELCOLOR(leveldir_new);
3174 
3175   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3176 
3177   leveldir_new->handicap_level =	/* set handicap to default value */
3178     (leveldir_new->user_defined || !leveldir_new->handicap ?
3179      leveldir_new->last_level : leveldir_new->first_level);
3180 
3181 #if 1
3182 #if 1
3183   DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3184 		  leveldir_new->level_group);
3185 #else
3186   if (leveldir_new->level_group ||
3187       DelayReached(&progress_delay, progress_delay_value))
3188     DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3189 #endif
3190 #else
3191   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3192 #endif
3193 
3194 #if 0
3195   /* !!! don't skip sets without levels (else artwork base sets are missing) */
3196 #if 1
3197   if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3198   {
3199     /* skip level sets without levels (which are probably artwork base sets) */
3200 
3201     freeSetupFileHash(setup_file_hash);
3202     free(directory_path);
3203     free(filename);
3204 
3205     return FALSE;
3206   }
3207 #endif
3208 #endif
3209 
3210   pushTreeInfo(node_first, leveldir_new);
3211 
3212   freeSetupFileHash(setup_file_hash);
3213 
3214   if (leveldir_new->level_group)
3215   {
3216     /* create node to link back to current level directory */
3217     createParentTreeInfoNode(leveldir_new);
3218 
3219     /* recursively step into sub-directory and look for more level series */
3220     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3221 			      leveldir_new, directory_path);
3222   }
3223 
3224   free(directory_path);
3225   free(filename);
3226 
3227   return TRUE;
3228 }
3229 
LoadLevelInfoFromLevelDir(TreeInfo ** node_first,TreeInfo * node_parent,char * level_directory)3230 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3231 				      TreeInfo *node_parent,
3232 				      char *level_directory)
3233 {
3234   DIR *dir;
3235   struct dirent *dir_entry;
3236   boolean valid_entry_found = FALSE;
3237 
3238   if ((dir = opendir(level_directory)) == NULL)
3239   {
3240     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3241     return;
3242   }
3243 
3244   while ((dir_entry = readdir(dir)) != NULL)	/* loop until last dir entry */
3245   {
3246     struct stat file_status;
3247     char *directory_name = dir_entry->d_name;
3248     char *directory_path = getPath2(level_directory, directory_name);
3249 
3250     /* skip entries for current and parent directory */
3251     if (strEqual(directory_name, ".") ||
3252 	strEqual(directory_name, ".."))
3253     {
3254       free(directory_path);
3255       continue;
3256     }
3257 
3258     /* find out if directory entry is itself a directory */
3259     if (stat(directory_path, &file_status) != 0 ||	/* cannot stat file */
3260 	(file_status.st_mode & S_IFMT) != S_IFDIR)	/* not a directory */
3261     {
3262       free(directory_path);
3263       continue;
3264     }
3265 
3266     free(directory_path);
3267 
3268     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3269 	strEqual(directory_name, SOUNDS_DIRECTORY) ||
3270 	strEqual(directory_name, MUSIC_DIRECTORY))
3271       continue;
3272 
3273     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3274 						    level_directory,
3275 						    directory_name);
3276   }
3277 
3278   closedir(dir);
3279 
3280   /* special case: top level directory may directly contain "levelinfo.conf" */
3281   if (node_parent == NULL && !valid_entry_found)
3282   {
3283     /* check if this directory directly contains a file "levelinfo.conf" */
3284     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3285 						    level_directory, ".");
3286   }
3287 
3288   if (!valid_entry_found)
3289     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3290 	  level_directory);
3291 }
3292 
AdjustGraphicsForEMC()3293 boolean AdjustGraphicsForEMC()
3294 {
3295   boolean settings_changed = FALSE;
3296 
3297   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3298   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3299 
3300   return settings_changed;
3301 }
3302 
LoadLevelInfo()3303 void LoadLevelInfo()
3304 {
3305   InitUserLevelDirectory(getLoginName());
3306 
3307   DrawInitText("Loading level series", 120, FC_GREEN);
3308 
3309   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3310   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3311 
3312   /* after loading all level set information, clone the level directory tree
3313      and remove all level sets without levels (these may still contain artwork
3314      to be offered in the setup menu as "custom artwork", and are therefore
3315      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3316   leveldir_first_all = leveldir_first;
3317   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3318 
3319   AdjustGraphicsForEMC();
3320 
3321   /* before sorting, the first entries will be from the user directory */
3322   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3323 
3324   if (leveldir_first == NULL)
3325     Error(ERR_EXIT, "cannot find any valid level series in any directory");
3326 
3327   sortTreeInfo(&leveldir_first);
3328 
3329 #if 0
3330   dumpTreeInfo(leveldir_first, 0);
3331 #endif
3332 }
3333 
LoadArtworkInfoFromArtworkConf(TreeInfo ** node_first,TreeInfo * node_parent,char * base_directory,char * directory_name,int type)3334 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3335 					      TreeInfo *node_parent,
3336 					      char *base_directory,
3337 					      char *directory_name, int type)
3338 {
3339   char *directory_path = getPath2(base_directory, directory_name);
3340   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3341   SetupFileHash *setup_file_hash = NULL;
3342   TreeInfo *artwork_new = NULL;
3343   int i;
3344 
3345   if (fileExists(filename))
3346     setup_file_hash = loadSetupFileHash(filename);
3347 
3348   if (setup_file_hash == NULL)	/* no config file -- look for artwork files */
3349   {
3350     DIR *dir;
3351     struct dirent *dir_entry;
3352     boolean valid_file_found = FALSE;
3353 
3354     if ((dir = opendir(directory_path)) != NULL)
3355     {
3356       while ((dir_entry = readdir(dir)) != NULL)
3357       {
3358 	char *entry_name = dir_entry->d_name;
3359 
3360 	if (FileIsArtworkType(entry_name, type))
3361 	{
3362 	  valid_file_found = TRUE;
3363 	  break;
3364 	}
3365       }
3366 
3367       closedir(dir);
3368     }
3369 
3370     if (!valid_file_found)
3371     {
3372       if (!strEqual(directory_name, "."))
3373 	Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3374 
3375       free(directory_path);
3376       free(filename);
3377 
3378       return FALSE;
3379     }
3380   }
3381 
3382   artwork_new = newTreeInfo();
3383 
3384   if (node_parent)
3385     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3386   else
3387     setTreeInfoToDefaults(artwork_new, type);
3388 
3389   artwork_new->subdir = getStringCopy(directory_name);
3390 
3391   if (setup_file_hash)	/* (before defining ".color" and ".class_desc") */
3392   {
3393 #if 0
3394     checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3395 #endif
3396 
3397     /* set all structure fields according to the token/value pairs */
3398     ldi = *artwork_new;
3399     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3400       setSetupInfo(levelinfo_tokens, i,
3401 		   getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3402     *artwork_new = ldi;
3403 
3404     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3405       setString(&artwork_new->name, artwork_new->subdir);
3406 
3407     if (artwork_new->identifier == NULL)
3408       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3409 
3410     if (artwork_new->name_sorting == NULL)
3411       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3412   }
3413 
3414   if (node_parent == NULL)		/* top level group */
3415   {
3416     artwork_new->basepath = getStringCopy(base_directory);
3417     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3418   }
3419   else					/* sub level group */
3420   {
3421     artwork_new->basepath = getStringCopy(node_parent->basepath);
3422     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3423   }
3424 
3425   artwork_new->in_user_dir =
3426     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3427 
3428   /* (may use ".sort_priority" from "setup_file_hash" above) */
3429   artwork_new->color = ARTWORKCOLOR(artwork_new);
3430 
3431   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3432 
3433   if (setup_file_hash == NULL)	/* (after determining ".user_defined") */
3434   {
3435     if (strEqual(artwork_new->subdir, "."))
3436     {
3437       if (artwork_new->user_defined)
3438       {
3439 	setString(&artwork_new->identifier, "private");
3440 	artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3441       }
3442       else
3443       {
3444 	setString(&artwork_new->identifier, "classic");
3445 	artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3446       }
3447 
3448       /* set to new values after changing ".sort_priority" */
3449       artwork_new->color = ARTWORKCOLOR(artwork_new);
3450 
3451       setString(&artwork_new->class_desc,
3452 		getLevelClassDescription(artwork_new));
3453     }
3454     else
3455     {
3456       setString(&artwork_new->identifier, artwork_new->subdir);
3457     }
3458 
3459     setString(&artwork_new->name, artwork_new->identifier);
3460     setString(&artwork_new->name_sorting, artwork_new->name);
3461   }
3462 
3463 #if 0
3464   DrawInitText(artwork_new->name, 150, FC_YELLOW);
3465 #endif
3466 
3467   pushTreeInfo(node_first, artwork_new);
3468 
3469   freeSetupFileHash(setup_file_hash);
3470 
3471   free(directory_path);
3472   free(filename);
3473 
3474   return TRUE;
3475 }
3476 
LoadArtworkInfoFromArtworkDir(TreeInfo ** node_first,TreeInfo * node_parent,char * base_directory,int type)3477 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3478 					  TreeInfo *node_parent,
3479 					  char *base_directory, int type)
3480 {
3481   DIR *dir;
3482   struct dirent *dir_entry;
3483   boolean valid_entry_found = FALSE;
3484 
3485   if ((dir = opendir(base_directory)) == NULL)
3486   {
3487     /* display error if directory is main "options.graphics_directory" etc. */
3488     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3489       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3490 
3491     return;
3492   }
3493 
3494   while ((dir_entry = readdir(dir)) != NULL)	/* loop until last dir entry */
3495   {
3496     struct stat file_status;
3497     char *directory_name = dir_entry->d_name;
3498     char *directory_path = getPath2(base_directory, directory_name);
3499 
3500     /* skip directory entries for current and parent directory */
3501     if (strEqual(directory_name, ".") ||
3502 	strEqual(directory_name, ".."))
3503     {
3504       free(directory_path);
3505       continue;
3506     }
3507 
3508     /* skip directory entries which are not a directory or are not accessible */
3509     if (stat(directory_path, &file_status) != 0 ||	/* cannot stat file */
3510 	(file_status.st_mode & S_IFMT) != S_IFDIR)	/* not a directory */
3511     {
3512       free(directory_path);
3513       continue;
3514     }
3515 
3516     free(directory_path);
3517 
3518     /* check if this directory contains artwork with or without config file */
3519     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3520 							base_directory,
3521 							directory_name, type);
3522   }
3523 
3524   closedir(dir);
3525 
3526   /* check if this directory directly contains artwork itself */
3527   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3528 						      base_directory, ".",
3529 						      type);
3530   if (!valid_entry_found)
3531     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3532 	  base_directory);
3533 }
3534 
getDummyArtworkInfo(int type)3535 static TreeInfo *getDummyArtworkInfo(int type)
3536 {
3537   /* this is only needed when there is completely no artwork available */
3538   TreeInfo *artwork_new = newTreeInfo();
3539 
3540   setTreeInfoToDefaults(artwork_new, type);
3541 
3542   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3543   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3544   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3545 
3546   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3547   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3548   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3549 
3550   return artwork_new;
3551 }
3552 
LoadArtworkInfo()3553 void LoadArtworkInfo()
3554 {
3555   LoadArtworkInfoCache();
3556 
3557   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3558 
3559   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3560 				options.graphics_directory,
3561 				TREE_TYPE_GRAPHICS_DIR);
3562   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3563 				getUserGraphicsDir(),
3564 				TREE_TYPE_GRAPHICS_DIR);
3565 
3566   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3567 				options.sounds_directory,
3568 				TREE_TYPE_SOUNDS_DIR);
3569   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3570 				getUserSoundsDir(),
3571 				TREE_TYPE_SOUNDS_DIR);
3572 
3573   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3574 				options.music_directory,
3575 				TREE_TYPE_MUSIC_DIR);
3576   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3577 				getUserMusicDir(),
3578 				TREE_TYPE_MUSIC_DIR);
3579 
3580   if (artwork.gfx_first == NULL)
3581     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3582   if (artwork.snd_first == NULL)
3583     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3584   if (artwork.mus_first == NULL)
3585     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3586 
3587   /* before sorting, the first entries will be from the user directory */
3588   artwork.gfx_current =
3589     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3590   if (artwork.gfx_current == NULL)
3591     artwork.gfx_current =
3592       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3593   if (artwork.gfx_current == NULL)
3594     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3595 
3596   artwork.snd_current =
3597     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3598   if (artwork.snd_current == NULL)
3599     artwork.snd_current =
3600       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3601   if (artwork.snd_current == NULL)
3602     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3603 
3604   artwork.mus_current =
3605     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3606   if (artwork.mus_current == NULL)
3607     artwork.mus_current =
3608       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3609   if (artwork.mus_current == NULL)
3610     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3611 
3612   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3613   artwork.snd_current_identifier = artwork.snd_current->identifier;
3614   artwork.mus_current_identifier = artwork.mus_current->identifier;
3615 
3616 #if 0
3617   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3618   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3619   printf("music set == %s\n\n", artwork.mus_current_identifier);
3620 #endif
3621 
3622   sortTreeInfo(&artwork.gfx_first);
3623   sortTreeInfo(&artwork.snd_first);
3624   sortTreeInfo(&artwork.mus_first);
3625 
3626 #if 0
3627   dumpTreeInfo(artwork.gfx_first, 0);
3628   dumpTreeInfo(artwork.snd_first, 0);
3629   dumpTreeInfo(artwork.mus_first, 0);
3630 #endif
3631 }
3632 
LoadArtworkInfoFromLevelInfo(ArtworkDirTree ** artwork_node,LevelDirTree * level_node)3633 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3634 				  LevelDirTree *level_node)
3635 {
3636 #if 0
3637   static unsigned int progress_delay = 0;
3638   unsigned int progress_delay_value = 100;	/* (in milliseconds) */
3639 #endif
3640   int type = (*artwork_node)->type;
3641 
3642   /* recursively check all level directories for artwork sub-directories */
3643 
3644   while (level_node)
3645   {
3646     /* check all tree entries for artwork, but skip parent link entries */
3647     if (!level_node->parent_link)
3648     {
3649       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3650       boolean cached = (artwork_new != NULL);
3651 
3652       if (cached)
3653       {
3654 	pushTreeInfo(artwork_node, artwork_new);
3655       }
3656       else
3657       {
3658 	TreeInfo *topnode_last = *artwork_node;
3659 	char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3660 			      ARTWORK_DIRECTORY(type));
3661 
3662 	LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3663 
3664 	if (topnode_last != *artwork_node)	/* check for newly added node */
3665 	{
3666 	  artwork_new = *artwork_node;
3667 
3668 	  setString(&artwork_new->identifier,   level_node->subdir);
3669 	  setString(&artwork_new->name,         level_node->name);
3670 	  setString(&artwork_new->name_sorting, level_node->name_sorting);
3671 
3672 	  artwork_new->sort_priority = level_node->sort_priority;
3673 	  artwork_new->color = LEVELCOLOR(artwork_new);
3674 	}
3675 
3676 	free(path);
3677       }
3678 
3679       /* insert artwork info (from old cache or filesystem) into new cache */
3680       if (artwork_new != NULL)
3681 	setArtworkInfoCacheEntry(artwork_new, level_node, type);
3682     }
3683 
3684 #if 1
3685     DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3686 		    level_node->level_group);
3687 #else
3688     if (level_node->level_group ||
3689 	DelayReached(&progress_delay, progress_delay_value))
3690       DrawInitText(level_node->name, 150, FC_YELLOW);
3691 #endif
3692 
3693     if (level_node->node_group != NULL)
3694       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3695 
3696     level_node = level_node->next;
3697   }
3698 }
3699 
LoadLevelArtworkInfo()3700 void LoadLevelArtworkInfo()
3701 {
3702   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3703 
3704   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3705   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3706   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3707 
3708   SaveArtworkInfoCache();
3709 
3710   /* needed for reloading level artwork not known at ealier stage */
3711 
3712   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3713   {
3714     artwork.gfx_current =
3715       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3716     if (artwork.gfx_current == NULL)
3717       artwork.gfx_current =
3718 	getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3719     if (artwork.gfx_current == NULL)
3720       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3721   }
3722 
3723   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3724   {
3725     artwork.snd_current =
3726       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3727     if (artwork.snd_current == NULL)
3728       artwork.snd_current =
3729 	getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3730     if (artwork.snd_current == NULL)
3731       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3732   }
3733 
3734   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3735   {
3736     artwork.mus_current =
3737       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3738     if (artwork.mus_current == NULL)
3739       artwork.mus_current =
3740 	getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3741     if (artwork.mus_current == NULL)
3742       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3743   }
3744 
3745   sortTreeInfo(&artwork.gfx_first);
3746   sortTreeInfo(&artwork.snd_first);
3747   sortTreeInfo(&artwork.mus_first);
3748 
3749 #if 0
3750   dumpTreeInfo(artwork.gfx_first, 0);
3751   dumpTreeInfo(artwork.snd_first, 0);
3752   dumpTreeInfo(artwork.mus_first, 0);
3753 #endif
3754 }
3755 
SaveUserLevelInfo()3756 static void SaveUserLevelInfo()
3757 {
3758   LevelDirTree *level_info;
3759   char *filename;
3760   FILE *file;
3761   int i;
3762 
3763   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3764 
3765   if (!(file = fopen(filename, MODE_WRITE)))
3766   {
3767     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3768     free(filename);
3769     return;
3770   }
3771 
3772   level_info = newTreeInfo();
3773 
3774   /* always start with reliable default values */
3775   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3776 
3777   setString(&level_info->name, getLoginName());
3778   setString(&level_info->author, getRealName());
3779   level_info->levels = 100;
3780   level_info->first_level = 1;
3781 
3782   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3783 
3784   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3785 						 getCookie("LEVELINFO")));
3786 
3787   ldi = *level_info;
3788   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3789   {
3790     if (i == LEVELINFO_TOKEN_NAME ||
3791 	i == LEVELINFO_TOKEN_AUTHOR ||
3792 	i == LEVELINFO_TOKEN_LEVELS ||
3793 	i == LEVELINFO_TOKEN_FIRST_LEVEL)
3794       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3795 
3796     /* just to make things nicer :) */
3797     if (i == LEVELINFO_TOKEN_AUTHOR)
3798       fprintf(file, "\n");
3799   }
3800 
3801   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3802 
3803   fclose(file);
3804 
3805   SetFilePermissions(filename, PERMS_PRIVATE);
3806 
3807   freeTreeInfo(level_info);
3808   free(filename);
3809 }
3810 
getSetupValue(int type,void * value)3811 char *getSetupValue(int type, void *value)
3812 {
3813   static char value_string[MAX_LINE_LEN];
3814 
3815   if (value == NULL)
3816     return NULL;
3817 
3818   switch (type)
3819   {
3820     case TYPE_BOOLEAN:
3821       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3822       break;
3823 
3824     case TYPE_SWITCH:
3825       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3826       break;
3827 
3828     case TYPE_SWITCH3:
3829       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3830 			    *(int *)value == FALSE ? "off" : "on"));
3831       break;
3832 
3833     case TYPE_YES_NO:
3834       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3835       break;
3836 
3837     case TYPE_YES_NO_AUTO:
3838       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3839 			    *(int *)value == FALSE ? "no" : "yes"));
3840       break;
3841 
3842     case TYPE_ECS_AGA:
3843       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3844       break;
3845 
3846     case TYPE_KEY:
3847       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3848       break;
3849 
3850     case TYPE_KEY_X11:
3851       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3852       break;
3853 
3854     case TYPE_INTEGER:
3855       sprintf(value_string, "%d", *(int *)value);
3856       break;
3857 
3858     case TYPE_STRING:
3859       if (*(char **)value == NULL)
3860 	return NULL;
3861 
3862       strcpy(value_string, *(char **)value);
3863       break;
3864 
3865     default:
3866       value_string[0] = '\0';
3867       break;
3868   }
3869 
3870   if (type & TYPE_GHOSTED)
3871     strcpy(value_string, "n/a");
3872 
3873   return value_string;
3874 }
3875 
getSetupLine(struct TokenInfo * token_info,char * prefix,int token_nr)3876 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3877 {
3878   int i;
3879   char *line;
3880   static char token_string[MAX_LINE_LEN];
3881   int token_type = token_info[token_nr].type;
3882   void *setup_value = token_info[token_nr].value;
3883   char *token_text = token_info[token_nr].text;
3884   char *value_string = getSetupValue(token_type, setup_value);
3885 
3886   /* build complete token string */
3887   sprintf(token_string, "%s%s", prefix, token_text);
3888 
3889   /* build setup entry line */
3890   line = getFormattedSetupEntry(token_string, value_string);
3891 
3892   if (token_type == TYPE_KEY_X11)
3893   {
3894     Key key = *(Key *)setup_value;
3895     char *keyname = getKeyNameFromKey(key);
3896 
3897     /* add comment, if useful */
3898     if (!strEqual(keyname, "(undefined)") &&
3899 	!strEqual(keyname, "(unknown)"))
3900     {
3901       /* add at least one whitespace */
3902       strcat(line, " ");
3903       for (i = strlen(line); i < token_comment_position; i++)
3904 	strcat(line, " ");
3905 
3906       strcat(line, "# ");
3907       strcat(line, keyname);
3908     }
3909   }
3910 
3911   return line;
3912 }
3913 
LoadLevelSetup_LastSeries()3914 void LoadLevelSetup_LastSeries()
3915 {
3916   /* ----------------------------------------------------------------------- */
3917   /* ~/.<program>/levelsetup.conf                                            */
3918   /* ----------------------------------------------------------------------- */
3919 
3920   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3921   SetupFileHash *level_setup_hash = NULL;
3922 
3923   /* always start with reliable default values */
3924   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3925 
3926 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3927   leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3928 					       "jue_start");
3929   if (leveldir_current == NULL)
3930     leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3931 #endif
3932 
3933   if ((level_setup_hash = loadSetupFileHash(filename)))
3934   {
3935     char *last_level_series =
3936       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3937 
3938     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3939 						 last_level_series);
3940     if (leveldir_current == NULL)
3941       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3942 
3943     checkSetupFileHashIdentifier(level_setup_hash, filename,
3944 				 getCookie("LEVELSETUP"));
3945 
3946     freeSetupFileHash(level_setup_hash);
3947   }
3948   else
3949     Error(ERR_WARN, "using default setup values");
3950 
3951   free(filename);
3952 }
3953 
SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)3954 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3955 {
3956   /* ----------------------------------------------------------------------- */
3957   /* ~/.<program>/levelsetup.conf                                            */
3958   /* ----------------------------------------------------------------------- */
3959 
3960   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3961   char *level_subdir = leveldir_current->subdir;
3962   FILE *file;
3963 
3964   InitUserDataDirectory();
3965 
3966   if (!(file = fopen(filename, MODE_WRITE)))
3967   {
3968     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3969     free(filename);
3970     return;
3971   }
3972 
3973   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3974 						 getCookie("LEVELSETUP")));
3975 
3976   if (deactivate_last_level_series)
3977     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3978 
3979   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3980 					       level_subdir));
3981 
3982   fclose(file);
3983 
3984   SetFilePermissions(filename, PERMS_PRIVATE);
3985 
3986   free(filename);
3987 }
3988 
SaveLevelSetup_LastSeries()3989 void SaveLevelSetup_LastSeries()
3990 {
3991   SaveLevelSetup_LastSeries_Ext(FALSE);
3992 }
3993 
SaveLevelSetup_LastSeries_Deactivate()3994 void SaveLevelSetup_LastSeries_Deactivate()
3995 {
3996   SaveLevelSetup_LastSeries_Ext(TRUE);
3997 }
3998 
checkSeriesInfo()3999 static void checkSeriesInfo()
4000 {
4001   static char *level_directory = NULL;
4002   DIR *dir;
4003 #if 0
4004   struct dirent *dir_entry;
4005 #endif
4006 
4007   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4008 
4009   level_directory = getPath2((leveldir_current->in_user_dir ?
4010 			      getUserLevelDir(NULL) :
4011 			      options.level_directory),
4012 			     leveldir_current->fullpath);
4013 
4014   if ((dir = opendir(level_directory)) == NULL)
4015   {
4016     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4017 
4018     return;
4019   }
4020 
4021 #if 0
4022   while ((dir_entry = readdir(dir)) != NULL)	/* last directory entry */
4023   {
4024     if (strlen(dir_entry->d_name) > 4 &&
4025 	dir_entry->d_name[3] == '.' &&
4026 	strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4027     {
4028       char levelnum_str[4];
4029       int levelnum_value;
4030 
4031       strncpy(levelnum_str, dir_entry->d_name, 3);
4032       levelnum_str[3] = '\0';
4033 
4034       levelnum_value = atoi(levelnum_str);
4035 
4036       if (levelnum_value < leveldir_current->first_level)
4037       {
4038 	Error(ERR_WARN, "additional level %d found", levelnum_value);
4039 	leveldir_current->first_level = levelnum_value;
4040       }
4041       else if (levelnum_value > leveldir_current->last_level)
4042       {
4043 	Error(ERR_WARN, "additional level %d found", levelnum_value);
4044 	leveldir_current->last_level = levelnum_value;
4045       }
4046     }
4047   }
4048 #endif
4049 
4050   closedir(dir);
4051 }
4052 
LoadLevelSetup_SeriesInfo()4053 void LoadLevelSetup_SeriesInfo()
4054 {
4055   char *filename;
4056   SetupFileHash *level_setup_hash = NULL;
4057   char *level_subdir = leveldir_current->subdir;
4058   int i;
4059 
4060   /* always start with reliable default values */
4061   level_nr = leveldir_current->first_level;
4062 
4063   for (i = 0; i < MAX_LEVELS; i++)
4064   {
4065     LevelStats_setPlayed(i, 0);
4066     LevelStats_setSolved(i, 0);
4067   }
4068 
4069   checkSeriesInfo(leveldir_current);
4070 
4071   /* ----------------------------------------------------------------------- */
4072   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4073   /* ----------------------------------------------------------------------- */
4074 
4075   level_subdir = leveldir_current->subdir;
4076 
4077   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4078 
4079   if ((level_setup_hash = loadSetupFileHash(filename)))
4080   {
4081     char *token_value;
4082 
4083     /* get last played level in this level set */
4084 
4085     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4086 
4087     if (token_value)
4088     {
4089       level_nr = atoi(token_value);
4090 
4091       if (level_nr < leveldir_current->first_level)
4092 	level_nr = leveldir_current->first_level;
4093       if (level_nr > leveldir_current->last_level)
4094 	level_nr = leveldir_current->last_level;
4095     }
4096 
4097     /* get handicap level in this level set */
4098 
4099     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4100 
4101     if (token_value)
4102     {
4103       int level_nr = atoi(token_value);
4104 
4105       if (level_nr < leveldir_current->first_level)
4106 	level_nr = leveldir_current->first_level;
4107       if (level_nr > leveldir_current->last_level + 1)
4108 	level_nr = leveldir_current->last_level;
4109 
4110       if (leveldir_current->user_defined || !leveldir_current->handicap)
4111 	level_nr = leveldir_current->last_level;
4112 
4113       leveldir_current->handicap_level = level_nr;
4114     }
4115 
4116     /* get number of played and solved levels in this level set */
4117 
4118     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4119     {
4120       char *token = HASH_ITERATION_TOKEN(itr);
4121       char *value = HASH_ITERATION_VALUE(itr);
4122 
4123       if (strlen(token) == 3 &&
4124 	  token[0] >= '0' && token[0] <= '9' &&
4125 	  token[1] >= '0' && token[1] <= '9' &&
4126 	  token[2] >= '0' && token[2] <= '9')
4127       {
4128 	int level_nr = atoi(token);
4129 
4130 	if (value != NULL)
4131 	  LevelStats_setPlayed(level_nr, atoi(value));	/* read 1st column */
4132 
4133 	value = strchr(value, ' ');
4134 
4135 	if (value != NULL)
4136 	  LevelStats_setSolved(level_nr, atoi(value));	/* read 2nd column */
4137       }
4138     }
4139     END_HASH_ITERATION(hash, itr)
4140 
4141     checkSetupFileHashIdentifier(level_setup_hash, filename,
4142 				 getCookie("LEVELSETUP"));
4143 
4144     freeSetupFileHash(level_setup_hash);
4145   }
4146   else
4147     Error(ERR_WARN, "using default setup values");
4148 
4149   free(filename);
4150 }
4151 
SaveLevelSetup_SeriesInfo()4152 void SaveLevelSetup_SeriesInfo()
4153 {
4154   char *filename;
4155   char *level_subdir = leveldir_current->subdir;
4156   char *level_nr_str = int2str(level_nr, 0);
4157   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4158   FILE *file;
4159   int i;
4160 
4161   /* ----------------------------------------------------------------------- */
4162   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4163   /* ----------------------------------------------------------------------- */
4164 
4165   InitLevelSetupDirectory(level_subdir);
4166 
4167   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4168 
4169   if (!(file = fopen(filename, MODE_WRITE)))
4170   {
4171     Error(ERR_WARN, "cannot write setup file '%s'", filename);
4172     free(filename);
4173     return;
4174   }
4175 
4176   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4177 						 getCookie("LEVELSETUP")));
4178   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4179 					       level_nr_str));
4180   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4181 						 handicap_level_str));
4182 
4183   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4184        i++)
4185   {
4186     if (LevelStats_getPlayed(i) > 0 ||
4187 	LevelStats_getSolved(i) > 0)
4188     {
4189       char token[16];
4190       char value[16];
4191 
4192       sprintf(token, "%03d", i);
4193       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4194 
4195       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4196     }
4197   }
4198 
4199   fclose(file);
4200 
4201   SetFilePermissions(filename, PERMS_PRIVATE);
4202 
4203   free(filename);
4204 }
4205 
LevelStats_getPlayed(int nr)4206 int LevelStats_getPlayed(int nr)
4207 {
4208   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4209 }
4210 
LevelStats_getSolved(int nr)4211 int LevelStats_getSolved(int nr)
4212 {
4213   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4214 }
4215 
LevelStats_setPlayed(int nr,int value)4216 void LevelStats_setPlayed(int nr, int value)
4217 {
4218   if (nr >= 0 && nr < MAX_LEVELS)
4219     level_stats[nr].played = value;
4220 }
4221 
LevelStats_setSolved(int nr,int value)4222 void LevelStats_setSolved(int nr, int value)
4223 {
4224   if (nr >= 0 && nr < MAX_LEVELS)
4225     level_stats[nr].solved = value;
4226 }
4227 
LevelStats_incPlayed(int nr)4228 void LevelStats_incPlayed(int nr)
4229 {
4230   if (nr >= 0 && nr < MAX_LEVELS)
4231     level_stats[nr].played++;
4232 }
4233 
LevelStats_incSolved(int nr)4234 void LevelStats_incSolved(int nr)
4235 {
4236   if (nr >= 0 && nr < MAX_LEVELS)
4237     level_stats[nr].solved++;
4238 }
4239