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("subdir == '%s' ['%s', '%s'] [%d])\n",
1188 	   node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1189 
1190     if (node->node_group != NULL)
1191       dumpTreeInfo(node->node_group, depth + 1);
1192 
1193     node = node->next;
1194   }
1195 }
1196 
sortTreeInfoBySortFunction(TreeInfo ** node_first,int (* compare_function)(const void *,const void *))1197 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1198 				int (*compare_function)(const void *,
1199 							const void *))
1200 {
1201   int num_nodes = numTreeInfo(*node_first);
1202   TreeInfo **sort_array;
1203   TreeInfo *node = *node_first;
1204   int i = 0;
1205 
1206   if (num_nodes == 0)
1207     return;
1208 
1209   /* allocate array for sorting structure pointers */
1210   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1211 
1212   /* writing structure pointers to sorting array */
1213   while (i < num_nodes && node)		/* double boundary check... */
1214   {
1215     sort_array[i] = node;
1216 
1217     i++;
1218     node = node->next;
1219   }
1220 
1221   /* sorting the structure pointers in the sorting array */
1222   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1223 	compare_function);
1224 
1225   /* update the linkage of list elements with the sorted node array */
1226   for (i = 0; i < num_nodes - 1; i++)
1227     sort_array[i]->next = sort_array[i + 1];
1228   sort_array[num_nodes - 1]->next = NULL;
1229 
1230   /* update the linkage of the main list anchor pointer */
1231   *node_first = sort_array[0];
1232 
1233   free(sort_array);
1234 
1235   /* now recursively sort the level group structures */
1236   node = *node_first;
1237   while (node)
1238   {
1239     if (node->node_group != NULL)
1240       sortTreeInfoBySortFunction(&node->node_group, compare_function);
1241 
1242     node = node->next;
1243   }
1244 }
1245 
sortTreeInfo(TreeInfo ** node_first)1246 void sortTreeInfo(TreeInfo **node_first)
1247 {
1248   sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1249 }
1250 
1251 
1252 /* ========================================================================= */
1253 /* some stuff from "files.c"                                                 */
1254 /* ========================================================================= */
1255 
1256 #if defined(PLATFORM_WIN32)
1257 #ifndef S_IRGRP
1258 #define S_IRGRP S_IRUSR
1259 #endif
1260 #ifndef S_IROTH
1261 #define S_IROTH S_IRUSR
1262 #endif
1263 #ifndef S_IWGRP
1264 #define S_IWGRP S_IWUSR
1265 #endif
1266 #ifndef S_IWOTH
1267 #define S_IWOTH S_IWUSR
1268 #endif
1269 #ifndef S_IXGRP
1270 #define S_IXGRP S_IXUSR
1271 #endif
1272 #ifndef S_IXOTH
1273 #define S_IXOTH S_IXUSR
1274 #endif
1275 #ifndef S_IRWXG
1276 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1277 #endif
1278 #ifndef S_ISGID
1279 #define S_ISGID 0
1280 #endif
1281 #endif	/* PLATFORM_WIN32 */
1282 
1283 /* file permissions for newly written files */
1284 #define MODE_R_ALL		(S_IRUSR | S_IRGRP | S_IROTH)
1285 #define MODE_W_ALL		(S_IWUSR | S_IWGRP | S_IWOTH)
1286 #define MODE_X_ALL		(S_IXUSR | S_IXGRP | S_IXOTH)
1287 
1288 #define MODE_W_PRIVATE		(S_IWUSR)
1289 #define MODE_W_PUBLIC		(S_IWUSR | S_IWGRP)
1290 #define MODE_W_PUBLIC_DIR	(S_IWUSR | S_IWGRP | S_ISGID)
1291 
1292 #define DIR_PERMS_PRIVATE	(MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1293 #define DIR_PERMS_PUBLIC	(MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1294 
1295 #define FILE_PERMS_PRIVATE	(MODE_R_ALL | MODE_W_PRIVATE)
1296 #define FILE_PERMS_PUBLIC	(MODE_R_ALL | MODE_W_PUBLIC)
1297 
getHomeDir()1298 char *getHomeDir()
1299 {
1300   static char *dir = NULL;
1301 
1302 #if defined(PLATFORM_WIN32)
1303   if (dir == NULL)
1304   {
1305     dir = checked_malloc(MAX_PATH + 1);
1306 
1307     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1308       strcpy(dir, ".");
1309   }
1310 #elif defined(PLATFORM_UNIX)
1311   if (dir == NULL)
1312   {
1313     if ((dir = getenv("HOME")) == NULL)
1314     {
1315       struct passwd *pwd;
1316 
1317       if ((pwd = getpwuid(getuid())) != NULL)
1318 	dir = getStringCopy(pwd->pw_dir);
1319       else
1320 	dir = ".";
1321     }
1322   }
1323 #else
1324   dir = ".";
1325 #endif
1326 
1327   return dir;
1328 }
1329 
getCommonDataDir(void)1330 char *getCommonDataDir(void)
1331 {
1332   static char *common_data_dir = NULL;
1333 
1334 #if defined(PLATFORM_WIN32)
1335   if (common_data_dir == NULL)
1336   {
1337     char *dir = checked_malloc(MAX_PATH + 1);
1338 
1339     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1340 	&& !strEqual(dir, ""))		/* empty for Windows 95/98 */
1341       common_data_dir = getPath2(dir, program.userdata_subdir);
1342     else
1343       common_data_dir = options.rw_base_directory;
1344   }
1345 #else
1346   if (common_data_dir == NULL)
1347     common_data_dir = options.rw_base_directory;
1348 #endif
1349 
1350   return common_data_dir;
1351 }
1352 
getPersonalDataDir(void)1353 char *getPersonalDataDir(void)
1354 {
1355   static char *personal_data_dir = NULL;
1356 
1357 #if defined(PLATFORM_MACOSX)
1358   if (personal_data_dir == NULL)
1359     personal_data_dir = getPath2(getHomeDir(), "Documents");
1360 #else
1361   if (personal_data_dir == NULL)
1362     personal_data_dir = getHomeDir();
1363 #endif
1364 
1365   return personal_data_dir;
1366 }
1367 
getUserGameDataDir(void)1368 char *getUserGameDataDir(void)
1369 {
1370   static char *user_game_data_dir = NULL;
1371 
1372   if (user_game_data_dir == NULL)
1373     user_game_data_dir = getPath2(getPersonalDataDir(),
1374 				  program.userdata_subdir);
1375 
1376   return user_game_data_dir;
1377 }
1378 
updateUserGameDataDir()1379 void updateUserGameDataDir()
1380 {
1381 #if defined(PLATFORM_MACOSX)
1382   char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1383   char *userdata_dir_new = getUserGameDataDir();	/* do not free() this */
1384 
1385   /* convert old Unix style game data directory to Mac OS X style, if needed */
1386   if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1387   {
1388     if (rename(userdata_dir_old, userdata_dir_new) != 0)
1389     {
1390       Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1391 	    userdata_dir_old, userdata_dir_new);
1392 
1393       /* continue using Unix style data directory -- this should not happen */
1394       program.userdata_path = getPath2(getPersonalDataDir(),
1395 				       program.userdata_subdir_unix);
1396     }
1397   }
1398 
1399   free(userdata_dir_old);
1400 #endif
1401 }
1402 
getSetupDir()1403 char *getSetupDir()
1404 {
1405   return getUserGameDataDir();
1406 }
1407 
posix_umask(mode_t mask)1408 static mode_t posix_umask(mode_t mask)
1409 {
1410 #if defined(PLATFORM_UNIX)
1411   return umask(mask);
1412 #else
1413   return 0;
1414 #endif
1415 }
1416 
posix_mkdir(const char * pathname,mode_t mode)1417 static int posix_mkdir(const char *pathname, mode_t mode)
1418 {
1419 #if defined(PLATFORM_WIN32)
1420   return mkdir(pathname);
1421 #else
1422   return mkdir(pathname, mode);
1423 #endif
1424 }
1425 
posix_process_running_setgid()1426 static boolean posix_process_running_setgid()
1427 {
1428 #if defined(PLATFORM_UNIX)
1429   return (getgid() != getegid());
1430 #else
1431   return FALSE;
1432 #endif
1433 }
1434 
createDirectory(char * dir,char * text,int permission_class)1435 void createDirectory(char *dir, char *text, int permission_class)
1436 {
1437   /* leave "other" permissions in umask untouched, but ensure group parts
1438      of USERDATA_DIR_MODE are not masked */
1439   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1440 		     DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1441   mode_t last_umask = posix_umask(0);
1442   mode_t group_umask = ~(dir_mode & S_IRWXG);
1443   int running_setgid = posix_process_running_setgid();
1444 
1445   /* if we're setgid, protect files against "other" */
1446   /* else keep umask(0) to make the dir world-writable */
1447 
1448   if (running_setgid)
1449     posix_umask(last_umask & group_umask);
1450   else
1451     dir_mode |= MODE_W_ALL;
1452 
1453   if (!fileExists(dir))
1454     if (posix_mkdir(dir, dir_mode) != 0)
1455       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1456 
1457   if (permission_class == PERMS_PUBLIC && !running_setgid)
1458     chmod(dir, dir_mode);
1459 
1460   posix_umask(last_umask);		/* restore previous umask */
1461 }
1462 
InitUserDataDirectory()1463 void InitUserDataDirectory()
1464 {
1465   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1466 }
1467 
SetFilePermissions(char * filename,int permission_class)1468 void SetFilePermissions(char *filename, int permission_class)
1469 {
1470   int running_setgid = posix_process_running_setgid();
1471   int perms = (permission_class == PERMS_PRIVATE ?
1472 	       FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1473 
1474   if (permission_class == PERMS_PUBLIC && !running_setgid)
1475     perms |= MODE_W_ALL;
1476 
1477   chmod(filename, perms);
1478 }
1479 
getCookie(char * file_type)1480 char *getCookie(char *file_type)
1481 {
1482   static char cookie[MAX_COOKIE_LEN + 1];
1483 
1484   if (strlen(program.cookie_prefix) + 1 +
1485       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1486     return "[COOKIE ERROR]";	/* should never happen */
1487 
1488   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1489 	  program.cookie_prefix, file_type,
1490 	  program.version_major, program.version_minor);
1491 
1492   return cookie;
1493 }
1494 
getFileVersionFromCookieString(const char * cookie)1495 int getFileVersionFromCookieString(const char *cookie)
1496 {
1497   const char *ptr_cookie1, *ptr_cookie2;
1498   const char *pattern1 = "_FILE_VERSION_";
1499   const char *pattern2 = "?.?";
1500   const int len_cookie = strlen(cookie);
1501   const int len_pattern1 = strlen(pattern1);
1502   const int len_pattern2 = strlen(pattern2);
1503   const int len_pattern = len_pattern1 + len_pattern2;
1504   int version_major, version_minor;
1505 
1506   if (len_cookie <= len_pattern)
1507     return -1;
1508 
1509   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1510   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1511 
1512   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1513     return -1;
1514 
1515   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1516       ptr_cookie2[1] != '.' ||
1517       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1518     return -1;
1519 
1520   version_major = ptr_cookie2[0] - '0';
1521   version_minor = ptr_cookie2[2] - '0';
1522 
1523   return VERSION_IDENT(version_major, version_minor, 0, 0);
1524 }
1525 
checkCookieString(const char * cookie,const char * template)1526 boolean checkCookieString(const char *cookie, const char *template)
1527 {
1528   const char *pattern = "_FILE_VERSION_?.?";
1529   const int len_cookie = strlen(cookie);
1530   const int len_template = strlen(template);
1531   const int len_pattern = strlen(pattern);
1532 
1533   if (len_cookie != len_template)
1534     return FALSE;
1535 
1536   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1537     return FALSE;
1538 
1539   return TRUE;
1540 }
1541 
1542 /* ------------------------------------------------------------------------- */
1543 /* setup file list and hash handling functions                               */
1544 /* ------------------------------------------------------------------------- */
1545 
getFormattedSetupEntry(char * token,char * value)1546 char *getFormattedSetupEntry(char *token, char *value)
1547 {
1548   int i;
1549   static char entry[MAX_LINE_LEN];
1550 
1551   /* if value is an empty string, just return token without value */
1552   if (*value == '\0')
1553     return token;
1554 
1555   /* start with the token and some spaces to format output line */
1556   sprintf(entry, "%s:", token);
1557   for (i = strlen(entry); i < token_value_position; i++)
1558     strcat(entry, " ");
1559 
1560   /* continue with the token's value */
1561   strcat(entry, value);
1562 
1563   return entry;
1564 }
1565 
newSetupFileList(char * token,char * value)1566 SetupFileList *newSetupFileList(char *token, char *value)
1567 {
1568   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1569 
1570   new->token = getStringCopy(token);
1571   new->value = getStringCopy(value);
1572 
1573   new->next = NULL;
1574 
1575   return new;
1576 }
1577 
freeSetupFileList(SetupFileList * list)1578 void freeSetupFileList(SetupFileList *list)
1579 {
1580   if (list == NULL)
1581     return;
1582 
1583   checked_free(list->token);
1584   checked_free(list->value);
1585 
1586   if (list->next)
1587     freeSetupFileList(list->next);
1588 
1589   free(list);
1590 }
1591 
getListEntry(SetupFileList * list,char * token)1592 char *getListEntry(SetupFileList *list, char *token)
1593 {
1594   if (list == NULL)
1595     return NULL;
1596 
1597   if (strEqual(list->token, token))
1598     return list->value;
1599   else
1600     return getListEntry(list->next, token);
1601 }
1602 
setListEntry(SetupFileList * list,char * token,char * value)1603 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1604 {
1605   if (list == NULL)
1606     return NULL;
1607 
1608   if (strEqual(list->token, token))
1609   {
1610     checked_free(list->value);
1611 
1612     list->value = getStringCopy(value);
1613 
1614     return list;
1615   }
1616   else if (list->next == NULL)
1617     return (list->next = newSetupFileList(token, value));
1618   else
1619     return setListEntry(list->next, token, value);
1620 }
1621 
addListEntry(SetupFileList * list,char * token,char * value)1622 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1623 {
1624   if (list == NULL)
1625     return NULL;
1626 
1627   if (list->next == NULL)
1628     return (list->next = newSetupFileList(token, value));
1629   else
1630     return addListEntry(list->next, token, value);
1631 }
1632 
1633 #if 0
1634 #ifdef DEBUG
1635 static void printSetupFileList(SetupFileList *list)
1636 {
1637   if (!list)
1638     return;
1639 
1640   printf("token: '%s'\n", list->token);
1641   printf("value: '%s'\n", list->value);
1642 
1643   printSetupFileList(list->next);
1644 }
1645 #endif
1646 #endif
1647 
1648 #ifdef DEBUG
1649 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1650 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1651 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1652 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1653 #else
1654 #define insert_hash_entry hashtable_insert
1655 #define search_hash_entry hashtable_search
1656 #define change_hash_entry hashtable_change
1657 #define remove_hash_entry hashtable_remove
1658 #endif
1659 
get_hash_from_key(void * key)1660 unsigned int get_hash_from_key(void *key)
1661 {
1662   /*
1663     djb2
1664 
1665     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1666     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1667     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1668     it works better than many other constants, prime or not) has never been
1669     adequately explained.
1670 
1671     If you just want to have a good hash function, and cannot wait, djb2
1672     is one of the best string hash functions i know. It has excellent
1673     distribution and speed on many different sets of keys and table sizes.
1674     You are not likely to do better with one of the "well known" functions
1675     such as PJW, K&R, etc.
1676 
1677     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1678   */
1679 
1680   char *str = (char *)key;
1681   unsigned int hash = 5381;
1682   int c;
1683 
1684   while ((c = *str++))
1685     hash = ((hash << 5) + hash) + c;	/* hash * 33 + c */
1686 
1687   return hash;
1688 }
1689 
keys_are_equal(void * key1,void * key2)1690 static int keys_are_equal(void *key1, void *key2)
1691 {
1692   return (strEqual((char *)key1, (char *)key2));
1693 }
1694 
newSetupFileHash()1695 SetupFileHash *newSetupFileHash()
1696 {
1697   SetupFileHash *new_hash =
1698     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1699 
1700   if (new_hash == NULL)
1701     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1702 
1703   return new_hash;
1704 }
1705 
freeSetupFileHash(SetupFileHash * hash)1706 void freeSetupFileHash(SetupFileHash *hash)
1707 {
1708   if (hash == NULL)
1709     return;
1710 
1711   hashtable_destroy(hash, 1);	/* 1 == also free values stored in hash */
1712 }
1713 
getHashEntry(SetupFileHash * hash,char * token)1714 char *getHashEntry(SetupFileHash *hash, char *token)
1715 {
1716   if (hash == NULL)
1717     return NULL;
1718 
1719   return search_hash_entry(hash, token);
1720 }
1721 
setHashEntry(SetupFileHash * hash,char * token,char * value)1722 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1723 {
1724   char *value_copy;
1725 
1726   if (hash == NULL)
1727     return;
1728 
1729   value_copy = getStringCopy(value);
1730 
1731   /* change value; if it does not exist, insert it as new */
1732   if (!change_hash_entry(hash, token, value_copy))
1733     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1734       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1735 }
1736 
removeHashEntry(SetupFileHash * hash,char * token)1737 char *removeHashEntry(SetupFileHash *hash, char *token)
1738 {
1739   if (hash == NULL)
1740     return NULL;
1741 
1742   return remove_hash_entry(hash, token);
1743 }
1744 
1745 #if 0
1746 static void printSetupFileHash(SetupFileHash *hash)
1747 {
1748   BEGIN_HASH_ITERATION(hash, itr)
1749   {
1750     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1751     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1752   }
1753   END_HASH_ITERATION(hash, itr)
1754 }
1755 #endif
1756 
1757 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE		1
1758 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING		0
1759 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH		0
1760 
1761 static boolean token_value_separator_found = FALSE;
1762 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1763 static boolean token_value_separator_warning = FALSE;
1764 #endif
1765 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1766 static boolean token_already_exists_warning = FALSE;
1767 #endif
1768 
getTokenValueFromSetupLineExt(char * line,char ** token_ptr,char ** value_ptr,char * filename,char * line_raw,int line_nr,boolean separator_required)1769 static boolean getTokenValueFromSetupLineExt(char *line,
1770 					     char **token_ptr, char **value_ptr,
1771 					     char *filename, char *line_raw,
1772 					     int line_nr,
1773 					     boolean separator_required)
1774 {
1775   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1776   char *token, *value, *line_ptr;
1777 
1778   /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1779   if (line_raw == NULL)
1780   {
1781     strncpy(line_copy, line, MAX_LINE_LEN);
1782     line_copy[MAX_LINE_LEN] = '\0';
1783     line = line_copy;
1784 
1785     strcpy(line_raw_copy, line_copy);
1786     line_raw = line_raw_copy;
1787   }
1788 
1789   /* cut trailing comment from input line */
1790   for (line_ptr = line; *line_ptr; line_ptr++)
1791   {
1792     if (*line_ptr == '#')
1793     {
1794       *line_ptr = '\0';
1795       break;
1796     }
1797   }
1798 
1799   /* cut trailing whitespaces from input line */
1800   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1801     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1802       *line_ptr = '\0';
1803 
1804   /* ignore empty lines */
1805   if (*line == '\0')
1806     return FALSE;
1807 
1808   /* cut leading whitespaces from token */
1809   for (token = line; *token; token++)
1810     if (*token != ' ' && *token != '\t')
1811       break;
1812 
1813   /* start with empty value as reliable default */
1814   value = "";
1815 
1816   token_value_separator_found = FALSE;
1817 
1818   /* find end of token to determine start of value */
1819   for (line_ptr = token; *line_ptr; line_ptr++)
1820   {
1821 #if 1
1822     /* first look for an explicit token/value separator, like ':' or '=' */
1823     if (*line_ptr == ':' || *line_ptr == '=')
1824 #else
1825     if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1826 #endif
1827     {
1828       *line_ptr = '\0';			/* terminate token string */
1829       value = line_ptr + 1;		/* set beginning of value */
1830 
1831       token_value_separator_found = TRUE;
1832 
1833       break;
1834     }
1835   }
1836 
1837 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1838   /* fallback: if no token/value separator found, also allow whitespaces */
1839   if (!token_value_separator_found && !separator_required)
1840   {
1841     for (line_ptr = token; *line_ptr; line_ptr++)
1842     {
1843       if (*line_ptr == ' ' || *line_ptr == '\t')
1844       {
1845 	*line_ptr = '\0';		/* terminate token string */
1846 	value = line_ptr + 1;		/* set beginning of value */
1847 
1848 	token_value_separator_found = TRUE;
1849 
1850 	break;
1851       }
1852     }
1853 
1854 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1855     if (token_value_separator_found)
1856     {
1857       if (!token_value_separator_warning)
1858       {
1859 	Error(ERR_INFO_LINE, "-");
1860 
1861 	if (filename != NULL)
1862 	{
1863 	  Error(ERR_WARN, "missing token/value separator(s) in config file:");
1864 	  Error(ERR_INFO, "- config file: '%s'", filename);
1865 	}
1866 	else
1867 	{
1868 	  Error(ERR_WARN, "missing token/value separator(s):");
1869 	}
1870 
1871 	token_value_separator_warning = TRUE;
1872       }
1873 
1874       if (filename != NULL)
1875 	Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1876       else
1877 	Error(ERR_INFO, "- line: '%s'", line_raw);
1878     }
1879 #endif
1880   }
1881 #endif
1882 
1883   /* cut trailing whitespaces from token */
1884   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1885     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1886       *line_ptr = '\0';
1887 
1888   /* cut leading whitespaces from value */
1889   for (; *value; value++)
1890     if (*value != ' ' && *value != '\t')
1891       break;
1892 
1893 #if 0
1894   if (*value == '\0')
1895     value = "true";	/* treat tokens without value as "true" */
1896 #endif
1897 
1898   *token_ptr = token;
1899   *value_ptr = value;
1900 
1901   return TRUE;
1902 }
1903 
getTokenValueFromSetupLine(char * line,char ** token,char ** value)1904 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1905 {
1906   /* while the internal (old) interface does not require a token/value
1907      separator (for downwards compatibility with existing files which
1908      don't use them), it is mandatory for the external (new) interface */
1909 
1910   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1911 }
1912 
1913 #if 1
loadSetupFileData(void * setup_file_data,char * filename,boolean top_recursion_level,boolean is_hash)1914 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1915 				 boolean top_recursion_level, boolean is_hash)
1916 {
1917   static SetupFileHash *include_filename_hash = NULL;
1918   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1919   char *token, *value, *line_ptr;
1920   void *insert_ptr = NULL;
1921   boolean read_continued_line = FALSE;
1922   FILE *file;
1923   int line_nr = 0, token_count = 0, include_count = 0;
1924 
1925 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1926   token_value_separator_warning = FALSE;
1927 #endif
1928 
1929 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1930   token_already_exists_warning = FALSE;
1931 #endif
1932 
1933   if (!(file = fopen(filename, MODE_READ)))
1934   {
1935     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1936 
1937     return FALSE;
1938   }
1939 
1940   /* use "insert pointer" to store list end for constant insertion complexity */
1941   if (!is_hash)
1942     insert_ptr = setup_file_data;
1943 
1944   /* on top invocation, create hash to mark included files (to prevent loops) */
1945   if (top_recursion_level)
1946     include_filename_hash = newSetupFileHash();
1947 
1948   /* mark this file as already included (to prevent including it again) */
1949   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1950 
1951   while (!feof(file))
1952   {
1953     /* read next line of input file */
1954     if (!fgets(line, MAX_LINE_LEN, file))
1955       break;
1956 
1957     /* check if line was completely read and is terminated by line break */
1958     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1959       line_nr++;
1960 
1961     /* cut trailing line break (this can be newline and/or carriage return) */
1962     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1963       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1964 	*line_ptr = '\0';
1965 
1966     /* copy raw input line for later use (mainly debugging output) */
1967     strcpy(line_raw, line);
1968 
1969     if (read_continued_line)
1970     {
1971 #if 0
1972       /* !!! ??? WHY ??? !!! */
1973       /* cut leading whitespaces from input line */
1974       for (line_ptr = line; *line_ptr; line_ptr++)
1975 	if (*line_ptr != ' ' && *line_ptr != '\t')
1976 	  break;
1977 #endif
1978 
1979       /* append new line to existing line, if there is enough space */
1980       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1981 	strcat(previous_line, line_ptr);
1982 
1983       strcpy(line, previous_line);	/* copy storage buffer to line */
1984 
1985       read_continued_line = FALSE;
1986     }
1987 
1988     /* if the last character is '\', continue at next line */
1989     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1990     {
1991       line[strlen(line) - 1] = '\0';	/* cut off trailing backslash */
1992       strcpy(previous_line, line);	/* copy line to storage buffer */
1993 
1994       read_continued_line = TRUE;
1995 
1996       continue;
1997     }
1998 
1999     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2000 				       line_raw, line_nr, FALSE))
2001       continue;
2002 
2003     if (*token)
2004     {
2005       if (strEqual(token, "include"))
2006       {
2007 	if (getHashEntry(include_filename_hash, value) == NULL)
2008 	{
2009 	  char *basepath = getBasePath(filename);
2010 	  char *basename = getBaseName(value);
2011 	  char *filename_include = getPath2(basepath, basename);
2012 
2013 #if 0
2014 	  Error(ERR_INFO, "[including file '%s']", filename_include);
2015 #endif
2016 
2017 	  loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2018 
2019 	  free(basepath);
2020 	  free(basename);
2021 	  free(filename_include);
2022 
2023 	  include_count++;
2024 	}
2025 	else
2026 	{
2027 	  Error(ERR_WARN, "ignoring already processed file '%s'", value);
2028 	}
2029       }
2030       else
2031       {
2032 	if (is_hash)
2033 	{
2034 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2035 	  char *old_value =
2036 	    getHashEntry((SetupFileHash *)setup_file_data, token);
2037 
2038 	  if (old_value != NULL)
2039 	  {
2040 	    if (!token_already_exists_warning)
2041 	    {
2042 	      Error(ERR_INFO_LINE, "-");
2043 	      Error(ERR_WARN, "duplicate token(s) found in config file:");
2044 	      Error(ERR_INFO, "- config file: '%s'", filename);
2045 
2046 	      token_already_exists_warning = TRUE;
2047 	    }
2048 
2049 	    Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2050 	    Error(ERR_INFO, "  old value: '%s'", old_value);
2051 	    Error(ERR_INFO, "  new value: '%s'", value);
2052 	  }
2053 #endif
2054 
2055 	  setHashEntry((SetupFileHash *)setup_file_data, token, value);
2056 	}
2057 	else
2058 	{
2059 	  insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2060 	}
2061 
2062 	token_count++;
2063       }
2064     }
2065   }
2066 
2067   fclose(file);
2068 
2069 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2070   if (token_value_separator_warning)
2071     Error(ERR_INFO_LINE, "-");
2072 #endif
2073 
2074 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2075   if (token_already_exists_warning)
2076     Error(ERR_INFO_LINE, "-");
2077 #endif
2078 
2079   if (token_count == 0 && include_count == 0)
2080     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2081 
2082   if (top_recursion_level)
2083     freeSetupFileHash(include_filename_hash);
2084 
2085   return TRUE;
2086 }
2087 
2088 #else
2089 
loadSetupFileData(void * setup_file_data,char * filename,boolean top_recursion_level,boolean is_hash)2090 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2091 				 boolean top_recursion_level, boolean is_hash)
2092 {
2093   static SetupFileHash *include_filename_hash = NULL;
2094   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2095   char *token, *value, *line_ptr;
2096   void *insert_ptr = NULL;
2097   boolean read_continued_line = FALSE;
2098   FILE *file;
2099   int line_nr = 0;
2100   int token_count = 0;
2101 
2102 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2103   token_value_separator_warning = FALSE;
2104 #endif
2105 
2106   if (!(file = fopen(filename, MODE_READ)))
2107   {
2108     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2109 
2110     return FALSE;
2111   }
2112 
2113   /* use "insert pointer" to store list end for constant insertion complexity */
2114   if (!is_hash)
2115     insert_ptr = setup_file_data;
2116 
2117   /* on top invocation, create hash to mark included files (to prevent loops) */
2118   if (top_recursion_level)
2119     include_filename_hash = newSetupFileHash();
2120 
2121   /* mark this file as already included (to prevent including it again) */
2122   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2123 
2124   while (!feof(file))
2125   {
2126     /* read next line of input file */
2127     if (!fgets(line, MAX_LINE_LEN, file))
2128       break;
2129 
2130     /* check if line was completely read and is terminated by line break */
2131     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2132       line_nr++;
2133 
2134     /* cut trailing line break (this can be newline and/or carriage return) */
2135     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2136       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2137 	*line_ptr = '\0';
2138 
2139     /* copy raw input line for later use (mainly debugging output) */
2140     strcpy(line_raw, line);
2141 
2142     if (read_continued_line)
2143     {
2144       /* cut leading whitespaces from input line */
2145       for (line_ptr = line; *line_ptr; line_ptr++)
2146 	if (*line_ptr != ' ' && *line_ptr != '\t')
2147 	  break;
2148 
2149       /* append new line to existing line, if there is enough space */
2150       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2151 	strcat(previous_line, line_ptr);
2152 
2153       strcpy(line, previous_line);	/* copy storage buffer to line */
2154 
2155       read_continued_line = FALSE;
2156     }
2157 
2158     /* if the last character is '\', continue at next line */
2159     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2160     {
2161       line[strlen(line) - 1] = '\0';	/* cut off trailing backslash */
2162       strcpy(previous_line, line);	/* copy line to storage buffer */
2163 
2164       read_continued_line = TRUE;
2165 
2166       continue;
2167     }
2168 
2169     /* cut trailing comment from input line */
2170     for (line_ptr = line; *line_ptr; line_ptr++)
2171     {
2172       if (*line_ptr == '#')
2173       {
2174 	*line_ptr = '\0';
2175 	break;
2176       }
2177     }
2178 
2179     /* cut trailing whitespaces from input line */
2180     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2181       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2182 	*line_ptr = '\0';
2183 
2184     /* ignore empty lines */
2185     if (*line == '\0')
2186       continue;
2187 
2188     /* cut leading whitespaces from token */
2189     for (token = line; *token; token++)
2190       if (*token != ' ' && *token != '\t')
2191 	break;
2192 
2193     /* start with empty value as reliable default */
2194     value = "";
2195 
2196     token_value_separator_found = FALSE;
2197 
2198     /* find end of token to determine start of value */
2199     for (line_ptr = token; *line_ptr; line_ptr++)
2200     {
2201 #if 1
2202       /* first look for an explicit token/value separator, like ':' or '=' */
2203       if (*line_ptr == ':' || *line_ptr == '=')
2204 #else
2205       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2206 #endif
2207       {
2208 	*line_ptr = '\0';		/* terminate token string */
2209 	value = line_ptr + 1;		/* set beginning of value */
2210 
2211 	token_value_separator_found = TRUE;
2212 
2213 	break;
2214       }
2215     }
2216 
2217 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2218     /* fallback: if no token/value separator found, also allow whitespaces */
2219     if (!token_value_separator_found)
2220     {
2221       for (line_ptr = token; *line_ptr; line_ptr++)
2222       {
2223 	if (*line_ptr == ' ' || *line_ptr == '\t')
2224 	{
2225 	  *line_ptr = '\0';		/* terminate token string */
2226 	  value = line_ptr + 1;		/* set beginning of value */
2227 
2228 	  token_value_separator_found = TRUE;
2229 
2230 	  break;
2231 	}
2232       }
2233 
2234 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2235       if (token_value_separator_found)
2236       {
2237 	if (!token_value_separator_warning)
2238 	{
2239 	  Error(ERR_INFO_LINE, "-");
2240 	  Error(ERR_WARN, "missing token/value separator(s) in config file:");
2241 	  Error(ERR_INFO, "- config file: '%s'", filename);
2242 
2243 	  token_value_separator_warning = TRUE;
2244 	}
2245 
2246 	Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2247       }
2248 #endif
2249     }
2250 #endif
2251 
2252     /* cut trailing whitespaces from token */
2253     for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2254       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2255 	*line_ptr = '\0';
2256 
2257     /* cut leading whitespaces from value */
2258     for (; *value; value++)
2259       if (*value != ' ' && *value != '\t')
2260 	break;
2261 
2262 #if 0
2263     if (*value == '\0')
2264       value = "true";	/* treat tokens without value as "true" */
2265 #endif
2266 
2267     if (*token)
2268     {
2269       if (strEqual(token, "include"))
2270       {
2271 	if (getHashEntry(include_filename_hash, value) == NULL)
2272 	{
2273 	  char *basepath = getBasePath(filename);
2274 	  char *basename = getBaseName(value);
2275 	  char *filename_include = getPath2(basepath, basename);
2276 
2277 #if 0
2278 	  Error(ERR_INFO, "[including file '%s']", filename_include);
2279 #endif
2280 
2281 	  loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2282 
2283 	  free(basepath);
2284 	  free(basename);
2285 	  free(filename_include);
2286 	}
2287 	else
2288 	{
2289 	  Error(ERR_WARN, "ignoring already processed file '%s'", value);
2290 	}
2291       }
2292       else
2293       {
2294 	if (is_hash)
2295 	  setHashEntry((SetupFileHash *)setup_file_data, token, value);
2296 	else
2297 	  insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2298 
2299 	token_count++;
2300       }
2301     }
2302   }
2303 
2304   fclose(file);
2305 
2306 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2307   if (token_value_separator_warning)
2308     Error(ERR_INFO_LINE, "-");
2309 #endif
2310 
2311   if (token_count == 0)
2312     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2313 
2314   if (top_recursion_level)
2315     freeSetupFileHash(include_filename_hash);
2316 
2317   return TRUE;
2318 }
2319 #endif
2320 
saveSetupFileHash(SetupFileHash * hash,char * filename)2321 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2322 {
2323   FILE *file;
2324 
2325   if (!(file = fopen(filename, MODE_WRITE)))
2326   {
2327     Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2328 
2329     return;
2330   }
2331 
2332   BEGIN_HASH_ITERATION(hash, itr)
2333   {
2334     fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2335 						 HASH_ITERATION_VALUE(itr)));
2336   }
2337   END_HASH_ITERATION(hash, itr)
2338 
2339   fclose(file);
2340 }
2341 
loadSetupFileList(char * filename)2342 SetupFileList *loadSetupFileList(char *filename)
2343 {
2344   SetupFileList *setup_file_list = newSetupFileList("", "");
2345   SetupFileList *first_valid_list_entry;
2346 
2347   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2348   {
2349     freeSetupFileList(setup_file_list);
2350 
2351     return NULL;
2352   }
2353 
2354   first_valid_list_entry = setup_file_list->next;
2355 
2356   /* free empty list header */
2357   setup_file_list->next = NULL;
2358   freeSetupFileList(setup_file_list);
2359 
2360   return first_valid_list_entry;
2361 }
2362 
loadSetupFileHash(char * filename)2363 SetupFileHash *loadSetupFileHash(char *filename)
2364 {
2365   SetupFileHash *setup_file_hash = newSetupFileHash();
2366 
2367   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2368   {
2369     freeSetupFileHash(setup_file_hash);
2370 
2371     return NULL;
2372   }
2373 
2374   return setup_file_hash;
2375 }
2376 
checkSetupFileHashIdentifier(SetupFileHash * setup_file_hash,char * filename,char * identifier)2377 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2378 				  char *filename, char *identifier)
2379 {
2380   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2381 
2382   if (value == NULL)
2383     Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2384   else if (!checkCookieString(value, identifier))
2385     Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2386 }
2387 
2388 
2389 /* ========================================================================= */
2390 /* setup file stuff                                                          */
2391 /* ========================================================================= */
2392 
2393 #define TOKEN_STR_LAST_LEVEL_SERIES		"last_level_series"
2394 #define TOKEN_STR_LAST_PLAYED_LEVEL		"last_played_level"
2395 #define TOKEN_STR_HANDICAP_LEVEL		"handicap_level"
2396 
2397 /* level directory info */
2398 #define LEVELINFO_TOKEN_IDENTIFIER		0
2399 #define LEVELINFO_TOKEN_NAME			1
2400 #define LEVELINFO_TOKEN_NAME_SORTING		2
2401 #define LEVELINFO_TOKEN_AUTHOR			3
2402 #define LEVELINFO_TOKEN_YEAR			4
2403 #define LEVELINFO_TOKEN_IMPORTED_FROM		5
2404 #define LEVELINFO_TOKEN_IMPORTED_BY		6
2405 #define LEVELINFO_TOKEN_TESTED_BY		7
2406 #define LEVELINFO_TOKEN_LEVELS			8
2407 #define LEVELINFO_TOKEN_FIRST_LEVEL		9
2408 #define LEVELINFO_TOKEN_SORT_PRIORITY		10
2409 #define LEVELINFO_TOKEN_LATEST_ENGINE		11
2410 #define LEVELINFO_TOKEN_LEVEL_GROUP		12
2411 #define LEVELINFO_TOKEN_READONLY		13
2412 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS	14
2413 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA	15
2414 #define LEVELINFO_TOKEN_GRAPHICS_SET		16
2415 #define LEVELINFO_TOKEN_SOUNDS_SET		17
2416 #define LEVELINFO_TOKEN_MUSIC_SET		18
2417 #define LEVELINFO_TOKEN_FILENAME		19
2418 #define LEVELINFO_TOKEN_FILETYPE		20
2419 #define LEVELINFO_TOKEN_SPECIAL_FLAGS		21
2420 #define LEVELINFO_TOKEN_HANDICAP		22
2421 #define LEVELINFO_TOKEN_SKIP_LEVELS		23
2422 
2423 #define NUM_LEVELINFO_TOKENS			24
2424 
2425 static LevelDirTree ldi;
2426 
2427 static struct TokenInfo levelinfo_tokens[] =
2428 {
2429   /* level directory info */
2430   { TYPE_STRING,	&ldi.identifier,	"identifier"		},
2431   { TYPE_STRING,	&ldi.name,		"name"			},
2432   { TYPE_STRING,	&ldi.name_sorting,	"name_sorting"		},
2433   { TYPE_STRING,	&ldi.author,		"author"		},
2434   { TYPE_STRING,	&ldi.year,		"year"			},
2435   { TYPE_STRING,	&ldi.imported_from,	"imported_from"		},
2436   { TYPE_STRING,	&ldi.imported_by,	"imported_by"		},
2437   { TYPE_STRING,	&ldi.tested_by,		"tested_by"		},
2438   { TYPE_INTEGER,	&ldi.levels,		"levels"		},
2439   { TYPE_INTEGER,	&ldi.first_level,	"first_level"		},
2440   { TYPE_INTEGER,	&ldi.sort_priority,	"sort_priority"		},
2441   { TYPE_BOOLEAN,	&ldi.latest_engine,	"latest_engine"		},
2442   { TYPE_BOOLEAN,	&ldi.level_group,	"level_group"		},
2443   { TYPE_BOOLEAN,	&ldi.readonly,		"readonly"		},
2444   { TYPE_STRING,	&ldi.graphics_set_ecs,	"graphics_set.ecs"	},
2445   { TYPE_STRING,	&ldi.graphics_set_aga,	"graphics_set.aga"	},
2446   { TYPE_STRING,	&ldi.graphics_set,	"graphics_set"		},
2447   { TYPE_STRING,	&ldi.sounds_set,	"sounds_set"		},
2448   { TYPE_STRING,	&ldi.music_set,		"music_set"		},
2449   { TYPE_STRING,	&ldi.level_filename,	"filename"		},
2450   { TYPE_STRING,	&ldi.level_filetype,	"filetype"		},
2451   { TYPE_STRING,	&ldi.special_flags,	"special_flags"		},
2452   { TYPE_BOOLEAN,	&ldi.handicap,		"handicap"		},
2453   { TYPE_BOOLEAN,	&ldi.skip_levels,	"skip_levels"		}
2454 };
2455 
2456 static struct TokenInfo artworkinfo_tokens[] =
2457 {
2458   /* artwork directory info */
2459   { TYPE_STRING,	&ldi.identifier,	"identifier"		},
2460   { TYPE_STRING,	&ldi.subdir,		"subdir"		},
2461   { TYPE_STRING,	&ldi.name,		"name"			},
2462   { TYPE_STRING,	&ldi.name_sorting,	"name_sorting"		},
2463   { TYPE_STRING,	&ldi.author,		"author"		},
2464   { TYPE_INTEGER,	&ldi.sort_priority,	"sort_priority"		},
2465   { TYPE_STRING,	&ldi.basepath,		"basepath"		},
2466   { TYPE_STRING,	&ldi.fullpath,		"fullpath"		},
2467   { TYPE_BOOLEAN,	&ldi.in_user_dir,	"in_user_dir"		},
2468   { TYPE_INTEGER,	&ldi.color,		"color"			},
2469   { TYPE_STRING,	&ldi.class_desc,	"class_desc"		},
2470 
2471   { -1,			NULL,			NULL			},
2472 };
2473 
setTreeInfoToDefaults(TreeInfo * ti,int type)2474 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2475 {
2476   ti->type = type;
2477 
2478   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2479 		  ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2480 		  ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2481 		  ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2482 		  NULL);
2483 
2484   ti->node_parent = NULL;
2485   ti->node_group = NULL;
2486   ti->next = NULL;
2487 
2488   ti->cl_first = -1;
2489   ti->cl_cursor = -1;
2490 
2491   ti->subdir = NULL;
2492   ti->fullpath = NULL;
2493   ti->basepath = NULL;
2494   ti->identifier = NULL;
2495   ti->name = getStringCopy(ANONYMOUS_NAME);
2496   ti->name_sorting = NULL;
2497   ti->author = getStringCopy(ANONYMOUS_NAME);
2498   ti->year = NULL;
2499 
2500   ti->sort_priority = LEVELCLASS_UNDEFINED;	/* default: least priority */
2501   ti->latest_engine = FALSE;			/* default: get from level */
2502   ti->parent_link = FALSE;
2503   ti->in_user_dir = FALSE;
2504   ti->user_defined = FALSE;
2505   ti->color = 0;
2506   ti->class_desc = NULL;
2507 
2508   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2509 
2510   if (ti->type == TREE_TYPE_LEVEL_DIR)
2511   {
2512     ti->imported_from = NULL;
2513     ti->imported_by = NULL;
2514     ti->tested_by = NULL;
2515 
2516     ti->graphics_set_ecs = NULL;
2517     ti->graphics_set_aga = NULL;
2518     ti->graphics_set = NULL;
2519     ti->sounds_set = NULL;
2520     ti->music_set = NULL;
2521     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2522     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2523     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2524 
2525     ti->level_filename = NULL;
2526     ti->level_filetype = NULL;
2527 
2528     ti->special_flags = NULL;
2529 
2530     ti->levels = 0;
2531     ti->first_level = 0;
2532     ti->last_level = 0;
2533     ti->level_group = FALSE;
2534     ti->handicap_level = 0;
2535     ti->readonly = TRUE;
2536     ti->handicap = TRUE;
2537     ti->skip_levels = FALSE;
2538   }
2539 }
2540 
setTreeInfoToDefaultsFromParent(TreeInfo * ti,TreeInfo * parent)2541 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2542 {
2543   if (parent == NULL)
2544   {
2545     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2546 
2547     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2548 
2549     return;
2550   }
2551 
2552   /* copy all values from the parent structure */
2553 
2554   ti->type = parent->type;
2555 
2556   ti->node_top = parent->node_top;
2557   ti->node_parent = parent;
2558   ti->node_group = NULL;
2559   ti->next = NULL;
2560 
2561   ti->cl_first = -1;
2562   ti->cl_cursor = -1;
2563 
2564   ti->subdir = NULL;
2565   ti->fullpath = NULL;
2566   ti->basepath = NULL;
2567   ti->identifier = NULL;
2568   ti->name = getStringCopy(ANONYMOUS_NAME);
2569   ti->name_sorting = NULL;
2570   ti->author = getStringCopy(parent->author);
2571   ti->year = getStringCopy(parent->year);
2572 
2573   ti->sort_priority = parent->sort_priority;
2574   ti->latest_engine = parent->latest_engine;
2575   ti->parent_link = FALSE;
2576   ti->in_user_dir = parent->in_user_dir;
2577   ti->user_defined = parent->user_defined;
2578   ti->color = parent->color;
2579   ti->class_desc = getStringCopy(parent->class_desc);
2580 
2581   ti->infotext = getStringCopy(parent->infotext);
2582 
2583   if (ti->type == TREE_TYPE_LEVEL_DIR)
2584   {
2585     ti->imported_from = getStringCopy(parent->imported_from);
2586     ti->imported_by = getStringCopy(parent->imported_by);
2587     ti->tested_by = getStringCopy(parent->tested_by);
2588 
2589     ti->graphics_set_ecs = NULL;
2590     ti->graphics_set_aga = NULL;
2591     ti->graphics_set = NULL;
2592     ti->sounds_set = NULL;
2593     ti->music_set = NULL;
2594     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2595     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2596     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2597 
2598     ti->level_filename = NULL;
2599     ti->level_filetype = NULL;
2600 
2601     ti->special_flags = getStringCopy(parent->special_flags);
2602 
2603     ti->levels = 0;
2604     ti->first_level = 0;
2605     ti->last_level = 0;
2606     ti->level_group = FALSE;
2607     ti->handicap_level = 0;
2608 #if 1
2609     ti->readonly = parent->readonly;
2610 #else
2611     ti->readonly = TRUE;
2612 #endif
2613     ti->handicap = TRUE;
2614     ti->skip_levels = FALSE;
2615   }
2616 }
2617 
getTreeInfoCopy(TreeInfo * ti)2618 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2619 {
2620   TreeInfo *ti_copy = newTreeInfo();
2621 
2622   /* copy all values from the original structure */
2623 
2624   ti_copy->type			= ti->type;
2625 
2626   ti_copy->node_top		= ti->node_top;
2627   ti_copy->node_parent		= ti->node_parent;
2628   ti_copy->node_group		= ti->node_group;
2629   ti_copy->next			= ti->next;
2630 
2631   ti_copy->cl_first		= ti->cl_first;
2632   ti_copy->cl_cursor		= ti->cl_cursor;
2633 
2634   ti_copy->subdir		= getStringCopy(ti->subdir);
2635   ti_copy->fullpath		= getStringCopy(ti->fullpath);
2636   ti_copy->basepath		= getStringCopy(ti->basepath);
2637   ti_copy->identifier		= getStringCopy(ti->identifier);
2638   ti_copy->name			= getStringCopy(ti->name);
2639   ti_copy->name_sorting		= getStringCopy(ti->name_sorting);
2640   ti_copy->author		= getStringCopy(ti->author);
2641   ti_copy->year			= getStringCopy(ti->year);
2642   ti_copy->imported_from	= getStringCopy(ti->imported_from);
2643   ti_copy->imported_by		= getStringCopy(ti->imported_by);
2644   ti_copy->tested_by		= getStringCopy(ti->tested_by);
2645 
2646   ti_copy->graphics_set_ecs	= getStringCopy(ti->graphics_set_ecs);
2647   ti_copy->graphics_set_aga	= getStringCopy(ti->graphics_set_aga);
2648   ti_copy->graphics_set		= getStringCopy(ti->graphics_set);
2649   ti_copy->sounds_set		= getStringCopy(ti->sounds_set);
2650   ti_copy->music_set		= getStringCopy(ti->music_set);
2651   ti_copy->graphics_path	= getStringCopy(ti->graphics_path);
2652   ti_copy->sounds_path		= getStringCopy(ti->sounds_path);
2653   ti_copy->music_path		= getStringCopy(ti->music_path);
2654 
2655   ti_copy->level_filename	= getStringCopy(ti->level_filename);
2656   ti_copy->level_filetype	= getStringCopy(ti->level_filetype);
2657 
2658   ti_copy->special_flags	= getStringCopy(ti->special_flags);
2659 
2660   ti_copy->levels		= ti->levels;
2661   ti_copy->first_level		= ti->first_level;
2662   ti_copy->last_level		= ti->last_level;
2663   ti_copy->sort_priority	= ti->sort_priority;
2664 
2665   ti_copy->latest_engine	= ti->latest_engine;
2666 
2667   ti_copy->level_group		= ti->level_group;
2668   ti_copy->parent_link		= ti->parent_link;
2669   ti_copy->in_user_dir		= ti->in_user_dir;
2670   ti_copy->user_defined		= ti->user_defined;
2671   ti_copy->readonly		= ti->readonly;
2672   ti_copy->handicap		= ti->handicap;
2673   ti_copy->skip_levels		= ti->skip_levels;
2674 
2675   ti_copy->color		= ti->color;
2676   ti_copy->class_desc		= getStringCopy(ti->class_desc);
2677   ti_copy->handicap_level	= ti->handicap_level;
2678 
2679   ti_copy->infotext		= getStringCopy(ti->infotext);
2680 
2681   return ti_copy;
2682 }
2683 
freeTreeInfo(TreeInfo * ti)2684 void freeTreeInfo(TreeInfo *ti)
2685 {
2686   if (ti == NULL)
2687     return;
2688 
2689   checked_free(ti->subdir);
2690   checked_free(ti->fullpath);
2691   checked_free(ti->basepath);
2692   checked_free(ti->identifier);
2693 
2694   checked_free(ti->name);
2695   checked_free(ti->name_sorting);
2696   checked_free(ti->author);
2697   checked_free(ti->year);
2698 
2699   checked_free(ti->class_desc);
2700 
2701   checked_free(ti->infotext);
2702 
2703   if (ti->type == TREE_TYPE_LEVEL_DIR)
2704   {
2705     checked_free(ti->imported_from);
2706     checked_free(ti->imported_by);
2707     checked_free(ti->tested_by);
2708 
2709     checked_free(ti->graphics_set_ecs);
2710     checked_free(ti->graphics_set_aga);
2711     checked_free(ti->graphics_set);
2712     checked_free(ti->sounds_set);
2713     checked_free(ti->music_set);
2714 
2715     checked_free(ti->graphics_path);
2716     checked_free(ti->sounds_path);
2717     checked_free(ti->music_path);
2718 
2719     checked_free(ti->level_filename);
2720     checked_free(ti->level_filetype);
2721 
2722     checked_free(ti->special_flags);
2723   }
2724 
2725   checked_free(ti);
2726 }
2727 
setSetupInfo(struct TokenInfo * token_info,int token_nr,char * token_value)2728 void setSetupInfo(struct TokenInfo *token_info,
2729 		  int token_nr, char *token_value)
2730 {
2731   int token_type = token_info[token_nr].type;
2732   void *setup_value = token_info[token_nr].value;
2733 
2734   if (token_value == NULL)
2735     return;
2736 
2737   /* set setup field to corresponding token value */
2738   switch (token_type)
2739   {
2740     case TYPE_BOOLEAN:
2741     case TYPE_SWITCH:
2742       *(boolean *)setup_value = get_boolean_from_string(token_value);
2743       break;
2744 
2745     case TYPE_SWITCH3:
2746       *(int *)setup_value = get_switch3_from_string(token_value);
2747       break;
2748 
2749     case TYPE_KEY:
2750       *(Key *)setup_value = getKeyFromKeyName(token_value);
2751       break;
2752 
2753     case TYPE_KEY_X11:
2754       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2755       break;
2756 
2757     case TYPE_INTEGER:
2758       *(int *)setup_value = get_integer_from_string(token_value);
2759       break;
2760 
2761     case TYPE_STRING:
2762       checked_free(*(char **)setup_value);
2763       *(char **)setup_value = getStringCopy(token_value);
2764       break;
2765 
2766     default:
2767       break;
2768   }
2769 }
2770 
compareTreeInfoEntries(const void * object1,const void * object2)2771 static int compareTreeInfoEntries(const void *object1, const void *object2)
2772 {
2773   const TreeInfo *entry1 = *((TreeInfo **)object1);
2774   const TreeInfo *entry2 = *((TreeInfo **)object2);
2775   int class_sorting1, class_sorting2;
2776   int compare_result;
2777 
2778   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2779   {
2780     class_sorting1 = LEVELSORTING(entry1);
2781     class_sorting2 = LEVELSORTING(entry2);
2782   }
2783   else
2784   {
2785     class_sorting1 = ARTWORKSORTING(entry1);
2786     class_sorting2 = ARTWORKSORTING(entry2);
2787   }
2788 
2789   if (entry1->parent_link || entry2->parent_link)
2790     compare_result = (entry1->parent_link ? -1 : +1);
2791   else if (entry1->sort_priority == entry2->sort_priority)
2792   {
2793     char *name1 = getStringToLower(entry1->name_sorting);
2794     char *name2 = getStringToLower(entry2->name_sorting);
2795 
2796     compare_result = strcmp(name1, name2);
2797 
2798     free(name1);
2799     free(name2);
2800   }
2801   else if (class_sorting1 == class_sorting2)
2802     compare_result = entry1->sort_priority - entry2->sort_priority;
2803   else
2804     compare_result = class_sorting1 - class_sorting2;
2805 
2806   return compare_result;
2807 }
2808 
createParentTreeInfoNode(TreeInfo * node_parent)2809 static void createParentTreeInfoNode(TreeInfo *node_parent)
2810 {
2811   TreeInfo *ti_new;
2812 
2813   if (node_parent == NULL)
2814     return;
2815 
2816   ti_new = newTreeInfo();
2817   setTreeInfoToDefaults(ti_new, node_parent->type);
2818 
2819   ti_new->node_parent = node_parent;
2820   ti_new->parent_link = TRUE;
2821 
2822   setString(&ti_new->identifier, node_parent->identifier);
2823   setString(&ti_new->name, ".. (parent directory)");
2824   setString(&ti_new->name_sorting, ti_new->name);
2825 
2826   setString(&ti_new->subdir, "..");
2827   setString(&ti_new->fullpath, node_parent->fullpath);
2828 
2829   ti_new->sort_priority = node_parent->sort_priority;
2830   ti_new->latest_engine = node_parent->latest_engine;
2831 
2832   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2833 
2834   pushTreeInfo(&node_parent->node_group, ti_new);
2835 }
2836 
2837 
2838 /* -------------------------------------------------------------------------- */
2839 /* functions for handling level and custom artwork info cache                 */
2840 /* -------------------------------------------------------------------------- */
2841 
LoadArtworkInfoCache()2842 static void LoadArtworkInfoCache()
2843 {
2844   InitCacheDirectory();
2845 
2846   if (artworkinfo_cache_old == NULL)
2847   {
2848     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2849 
2850     /* try to load artwork info hash from already existing cache file */
2851     artworkinfo_cache_old = loadSetupFileHash(filename);
2852 
2853     /* if no artwork info cache file was found, start with empty hash */
2854     if (artworkinfo_cache_old == NULL)
2855       artworkinfo_cache_old = newSetupFileHash();
2856 
2857     free(filename);
2858   }
2859 
2860   if (artworkinfo_cache_new == NULL)
2861     artworkinfo_cache_new = newSetupFileHash();
2862 }
2863 
SaveArtworkInfoCache()2864 static void SaveArtworkInfoCache()
2865 {
2866   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2867 
2868   InitCacheDirectory();
2869 
2870   saveSetupFileHash(artworkinfo_cache_new, filename);
2871 
2872   free(filename);
2873 }
2874 
getCacheTokenPrefix(char * prefix1,char * prefix2)2875 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2876 {
2877   static char *prefix = NULL;
2878 
2879   checked_free(prefix);
2880 
2881   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2882 
2883   return prefix;
2884 }
2885 
2886 /* (identical to above function, but separate string buffer needed -- nasty) */
getCacheToken(char * prefix,char * suffix)2887 static char *getCacheToken(char *prefix, char *suffix)
2888 {
2889   static char *token = NULL;
2890 
2891   checked_free(token);
2892 
2893   token = getStringCat2WithSeparator(prefix, suffix, ".");
2894 
2895   return token;
2896 }
2897 
getFileTimestampString(char * filename)2898 static char *getFileTimestampString(char *filename)
2899 {
2900 #if 1
2901   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2902 #else
2903   struct stat file_status;
2904 
2905   if (stat(filename, &file_status) != 0)	/* cannot stat file */
2906     return getStringCopy(i_to_a(0));
2907 
2908   return getStringCopy(i_to_a(file_status.st_mtime));
2909 #endif
2910 }
2911 
modifiedFileTimestamp(char * filename,char * timestamp_string)2912 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2913 {
2914   struct stat file_status;
2915 
2916   if (timestamp_string == NULL)
2917     return TRUE;
2918 
2919   if (stat(filename, &file_status) != 0)	/* cannot stat file */
2920     return TRUE;
2921 
2922   return (file_status.st_mtime != atoi(timestamp_string));
2923 }
2924 
getArtworkInfoCacheEntry(LevelDirTree * level_node,int type)2925 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2926 {
2927   char *identifier = level_node->subdir;
2928   char *type_string = ARTWORK_DIRECTORY(type);
2929   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2930   char *token_main = getCacheToken(token_prefix, "CACHED");
2931   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2932   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2933   TreeInfo *artwork_info = NULL;
2934 
2935   if (!use_artworkinfo_cache)
2936     return NULL;
2937 
2938   if (cached)
2939   {
2940     int i;
2941 
2942     artwork_info = newTreeInfo();
2943     setTreeInfoToDefaults(artwork_info, type);
2944 
2945     /* set all structure fields according to the token/value pairs */
2946     ldi = *artwork_info;
2947     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2948     {
2949       char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2950       char *value = getHashEntry(artworkinfo_cache_old, token);
2951 
2952       setSetupInfo(artworkinfo_tokens, i, value);
2953 
2954       /* check if cache entry for this item is invalid or incomplete */
2955       if (value == NULL)
2956       {
2957 #if 1
2958 	Error(ERR_WARN, "cache entry '%s' invalid", token);
2959 #endif
2960 
2961 	cached = FALSE;
2962       }
2963     }
2964 
2965     *artwork_info = ldi;
2966   }
2967 
2968   if (cached)
2969   {
2970     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2971 					LEVELINFO_FILENAME);
2972     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2973 					  ARTWORKINFO_FILENAME(type));
2974 
2975     /* check if corresponding "levelinfo.conf" file has changed */
2976     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2977     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2978 
2979     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2980       cached = FALSE;
2981 
2982     /* check if corresponding "<artworkinfo>.conf" file has changed */
2983     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2984     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2985 
2986     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2987       cached = FALSE;
2988 
2989 #if 0
2990     if (!cached)
2991       printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2992 #endif
2993 
2994     checked_free(filename_levelinfo);
2995     checked_free(filename_artworkinfo);
2996   }
2997 
2998   if (!cached && artwork_info != NULL)
2999   {
3000     freeTreeInfo(artwork_info);
3001 
3002     return NULL;
3003   }
3004 
3005   return artwork_info;
3006 }
3007 
setArtworkInfoCacheEntry(TreeInfo * artwork_info,LevelDirTree * level_node,int type)3008 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3009 				     LevelDirTree *level_node, int type)
3010 {
3011   char *identifier = level_node->subdir;
3012   char *type_string = ARTWORK_DIRECTORY(type);
3013   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3014   char *token_main = getCacheToken(token_prefix, "CACHED");
3015   boolean set_cache_timestamps = TRUE;
3016   int i;
3017 
3018   setHashEntry(artworkinfo_cache_new, token_main, "true");
3019 
3020   if (set_cache_timestamps)
3021   {
3022     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3023 					LEVELINFO_FILENAME);
3024     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3025 					  ARTWORKINFO_FILENAME(type));
3026     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3027     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3028 
3029     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3030     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3031 
3032     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3033     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3034 
3035     checked_free(filename_levelinfo);
3036     checked_free(filename_artworkinfo);
3037     checked_free(timestamp_levelinfo);
3038     checked_free(timestamp_artworkinfo);
3039   }
3040 
3041   ldi = *artwork_info;
3042   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3043   {
3044     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3045     char *value = getSetupValue(artworkinfo_tokens[i].type,
3046 				artworkinfo_tokens[i].value);
3047     if (value != NULL)
3048       setHashEntry(artworkinfo_cache_new, token, value);
3049   }
3050 }
3051 
3052 
3053 /* -------------------------------------------------------------------------- */
3054 /* functions for loading level info and custom artwork info                   */
3055 /* -------------------------------------------------------------------------- */
3056 
3057 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3058 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3059 
LoadLevelInfoFromLevelConf(TreeInfo ** node_first,TreeInfo * node_parent,char * level_directory,char * directory_name)3060 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3061 					  TreeInfo *node_parent,
3062 					  char *level_directory,
3063 					  char *directory_name)
3064 {
3065 #if 0
3066   static unsigned int progress_delay = 0;
3067   unsigned int progress_delay_value = 100;	/* (in milliseconds) */
3068 #endif
3069   char *directory_path = getPath2(level_directory, directory_name);
3070   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3071   SetupFileHash *setup_file_hash;
3072   LevelDirTree *leveldir_new = NULL;
3073   int i;
3074 
3075   /* unless debugging, silently ignore directories without "levelinfo.conf" */
3076   if (!options.debug && !fileExists(filename))
3077   {
3078     free(directory_path);
3079     free(filename);
3080 
3081     return FALSE;
3082   }
3083 
3084   setup_file_hash = loadSetupFileHash(filename);
3085 
3086   if (setup_file_hash == NULL)
3087   {
3088     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3089 
3090     free(directory_path);
3091     free(filename);
3092 
3093     return FALSE;
3094   }
3095 
3096   leveldir_new = newTreeInfo();
3097 
3098   if (node_parent)
3099     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3100   else
3101     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3102 
3103   leveldir_new->subdir = getStringCopy(directory_name);
3104 
3105   checkSetupFileHashIdentifier(setup_file_hash, filename,
3106 			       getCookie("LEVELINFO"));
3107 
3108   /* set all structure fields according to the token/value pairs */
3109   ldi = *leveldir_new;
3110   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3111     setSetupInfo(levelinfo_tokens, i,
3112 		 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3113   *leveldir_new = ldi;
3114 
3115   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3116     setString(&leveldir_new->name, leveldir_new->subdir);
3117 
3118   if (leveldir_new->identifier == NULL)
3119     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3120 
3121   if (leveldir_new->name_sorting == NULL)
3122     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3123 
3124   if (node_parent == NULL)		/* top level group */
3125   {
3126     leveldir_new->basepath = getStringCopy(level_directory);
3127     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3128   }
3129   else					/* sub level group */
3130   {
3131     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3132     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3133   }
3134 
3135 #if 0
3136   if (leveldir_new->levels < 1)
3137     leveldir_new->levels = 1;
3138 #endif
3139 
3140   leveldir_new->last_level =
3141     leveldir_new->first_level + leveldir_new->levels - 1;
3142 
3143   leveldir_new->in_user_dir =
3144     (!strEqual(leveldir_new->basepath, options.level_directory));
3145 
3146 #if 0
3147   printf("::: '%s' -> %d\n",
3148 	 leveldir_new->identifier,
3149 	 leveldir_new->in_user_dir);
3150 #endif
3151 
3152   /* adjust some settings if user's private level directory was detected */
3153   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3154       leveldir_new->in_user_dir &&
3155       (strEqual(leveldir_new->subdir, getLoginName()) ||
3156        strEqual(leveldir_new->name,   getLoginName()) ||
3157        strEqual(leveldir_new->author, getRealName())))
3158   {
3159     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3160     leveldir_new->readonly = FALSE;
3161   }
3162 
3163   leveldir_new->user_defined =
3164     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3165 
3166   leveldir_new->color = LEVELCOLOR(leveldir_new);
3167 
3168   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3169 
3170   leveldir_new->handicap_level =	/* set handicap to default value */
3171     (leveldir_new->user_defined || !leveldir_new->handicap ?
3172      leveldir_new->last_level : leveldir_new->first_level);
3173 
3174 #if 1
3175 #if 1
3176   DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3177 		  leveldir_new->level_group);
3178 #else
3179   if (leveldir_new->level_group ||
3180       DelayReached(&progress_delay, progress_delay_value))
3181     DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3182 #endif
3183 #else
3184   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3185 #endif
3186 
3187 #if 0
3188   /* !!! don't skip sets without levels (else artwork base sets are missing) */
3189 #if 1
3190   if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3191   {
3192     /* skip level sets without levels (which are probably artwork base sets) */
3193 
3194     freeSetupFileHash(setup_file_hash);
3195     free(directory_path);
3196     free(filename);
3197 
3198     return FALSE;
3199   }
3200 #endif
3201 #endif
3202 
3203   pushTreeInfo(node_first, leveldir_new);
3204 
3205   freeSetupFileHash(setup_file_hash);
3206 
3207   if (leveldir_new->level_group)
3208   {
3209     /* create node to link back to current level directory */
3210     createParentTreeInfoNode(leveldir_new);
3211 
3212     /* recursively step into sub-directory and look for more level series */
3213     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3214 			      leveldir_new, directory_path);
3215   }
3216 
3217   free(directory_path);
3218   free(filename);
3219 
3220   return TRUE;
3221 }
3222 
LoadLevelInfoFromLevelDir(TreeInfo ** node_first,TreeInfo * node_parent,char * level_directory)3223 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3224 				      TreeInfo *node_parent,
3225 				      char *level_directory)
3226 {
3227   DIR *dir;
3228   struct dirent *dir_entry;
3229   boolean valid_entry_found = FALSE;
3230 
3231   if ((dir = opendir(level_directory)) == NULL)
3232   {
3233     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3234     return;
3235   }
3236 
3237   while ((dir_entry = readdir(dir)) != NULL)	/* loop until last dir entry */
3238   {
3239     struct stat file_status;
3240     char *directory_name = dir_entry->d_name;
3241     char *directory_path = getPath2(level_directory, directory_name);
3242 
3243     /* skip entries for current and parent directory */
3244     if (strEqual(directory_name, ".") ||
3245 	strEqual(directory_name, ".."))
3246     {
3247       free(directory_path);
3248       continue;
3249     }
3250 
3251     /* find out if directory entry is itself a directory */
3252     if (stat(directory_path, &file_status) != 0 ||	/* cannot stat file */
3253 	(file_status.st_mode & S_IFMT) != S_IFDIR)	/* not a directory */
3254     {
3255       free(directory_path);
3256       continue;
3257     }
3258 
3259     free(directory_path);
3260 
3261     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3262 	strEqual(directory_name, SOUNDS_DIRECTORY) ||
3263 	strEqual(directory_name, MUSIC_DIRECTORY))
3264       continue;
3265 
3266     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3267 						    level_directory,
3268 						    directory_name);
3269   }
3270 
3271   closedir(dir);
3272 
3273   /* special case: top level directory may directly contain "levelinfo.conf" */
3274   if (node_parent == NULL && !valid_entry_found)
3275   {
3276     /* check if this directory directly contains a file "levelinfo.conf" */
3277     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3278 						    level_directory, ".");
3279   }
3280 
3281   if (!valid_entry_found)
3282     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3283 	  level_directory);
3284 }
3285 
AdjustGraphicsForEMC()3286 boolean AdjustGraphicsForEMC()
3287 {
3288   boolean settings_changed = FALSE;
3289 
3290   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3291   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3292 
3293   return settings_changed;
3294 }
3295 
LoadLevelInfo()3296 void LoadLevelInfo()
3297 {
3298   InitUserLevelDirectory(getLoginName());
3299 
3300   DrawInitText("Loading level series", 120, FC_GREEN);
3301 
3302   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3303   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3304 
3305   /* after loading all level set information, clone the level directory tree
3306      and remove all level sets without levels (these may still contain artwork
3307      to be offered in the setup menu as "custom artwork", and are therefore
3308      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3309   leveldir_first_all = leveldir_first;
3310   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3311 
3312   AdjustGraphicsForEMC();
3313 
3314   /* before sorting, the first entries will be from the user directory */
3315   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3316 
3317   if (leveldir_first == NULL)
3318     Error(ERR_EXIT, "cannot find any valid level series in any directory");
3319 
3320   sortTreeInfo(&leveldir_first);
3321 
3322 #if 0
3323   dumpTreeInfo(leveldir_first, 0);
3324 #endif
3325 }
3326 
LoadArtworkInfoFromArtworkConf(TreeInfo ** node_first,TreeInfo * node_parent,char * base_directory,char * directory_name,int type)3327 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3328 					      TreeInfo *node_parent,
3329 					      char *base_directory,
3330 					      char *directory_name, int type)
3331 {
3332   char *directory_path = getPath2(base_directory, directory_name);
3333   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3334   SetupFileHash *setup_file_hash = NULL;
3335   TreeInfo *artwork_new = NULL;
3336   int i;
3337 
3338   if (fileExists(filename))
3339     setup_file_hash = loadSetupFileHash(filename);
3340 
3341   if (setup_file_hash == NULL)	/* no config file -- look for artwork files */
3342   {
3343     DIR *dir;
3344     struct dirent *dir_entry;
3345     boolean valid_file_found = FALSE;
3346 
3347     if ((dir = opendir(directory_path)) != NULL)
3348     {
3349       while ((dir_entry = readdir(dir)) != NULL)
3350       {
3351 	char *entry_name = dir_entry->d_name;
3352 
3353 	if (FileIsArtworkType(entry_name, type))
3354 	{
3355 	  valid_file_found = TRUE;
3356 	  break;
3357 	}
3358       }
3359 
3360       closedir(dir);
3361     }
3362 
3363     if (!valid_file_found)
3364     {
3365       if (!strEqual(directory_name, "."))
3366 	Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3367 
3368       free(directory_path);
3369       free(filename);
3370 
3371       return FALSE;
3372     }
3373   }
3374 
3375   artwork_new = newTreeInfo();
3376 
3377   if (node_parent)
3378     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3379   else
3380     setTreeInfoToDefaults(artwork_new, type);
3381 
3382   artwork_new->subdir = getStringCopy(directory_name);
3383 
3384   if (setup_file_hash)	/* (before defining ".color" and ".class_desc") */
3385   {
3386 #if 0
3387     checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3388 #endif
3389 
3390     /* set all structure fields according to the token/value pairs */
3391     ldi = *artwork_new;
3392     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3393       setSetupInfo(levelinfo_tokens, i,
3394 		   getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3395     *artwork_new = ldi;
3396 
3397     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3398       setString(&artwork_new->name, artwork_new->subdir);
3399 
3400     if (artwork_new->identifier == NULL)
3401       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3402 
3403     if (artwork_new->name_sorting == NULL)
3404       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3405   }
3406 
3407   if (node_parent == NULL)		/* top level group */
3408   {
3409     artwork_new->basepath = getStringCopy(base_directory);
3410     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3411   }
3412   else					/* sub level group */
3413   {
3414     artwork_new->basepath = getStringCopy(node_parent->basepath);
3415     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3416   }
3417 
3418   artwork_new->in_user_dir =
3419     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3420 
3421   /* (may use ".sort_priority" from "setup_file_hash" above) */
3422   artwork_new->color = ARTWORKCOLOR(artwork_new);
3423 
3424   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3425 
3426   if (setup_file_hash == NULL)	/* (after determining ".user_defined") */
3427   {
3428     if (strEqual(artwork_new->subdir, "."))
3429     {
3430       if (artwork_new->user_defined)
3431       {
3432 	setString(&artwork_new->identifier, "private");
3433 	artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3434       }
3435       else
3436       {
3437 	setString(&artwork_new->identifier, "classic");
3438 	artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3439       }
3440 
3441       /* set to new values after changing ".sort_priority" */
3442       artwork_new->color = ARTWORKCOLOR(artwork_new);
3443 
3444       setString(&artwork_new->class_desc,
3445 		getLevelClassDescription(artwork_new));
3446     }
3447     else
3448     {
3449       setString(&artwork_new->identifier, artwork_new->subdir);
3450     }
3451 
3452     setString(&artwork_new->name, artwork_new->identifier);
3453     setString(&artwork_new->name_sorting, artwork_new->name);
3454   }
3455 
3456 #if 0
3457   DrawInitText(artwork_new->name, 150, FC_YELLOW);
3458 #endif
3459 
3460   pushTreeInfo(node_first, artwork_new);
3461 
3462   freeSetupFileHash(setup_file_hash);
3463 
3464   free(directory_path);
3465   free(filename);
3466 
3467   return TRUE;
3468 }
3469 
LoadArtworkInfoFromArtworkDir(TreeInfo ** node_first,TreeInfo * node_parent,char * base_directory,int type)3470 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3471 					  TreeInfo *node_parent,
3472 					  char *base_directory, int type)
3473 {
3474   DIR *dir;
3475   struct dirent *dir_entry;
3476   boolean valid_entry_found = FALSE;
3477 
3478   if ((dir = opendir(base_directory)) == NULL)
3479   {
3480     /* display error if directory is main "options.graphics_directory" etc. */
3481     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3482       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3483 
3484     return;
3485   }
3486 
3487   while ((dir_entry = readdir(dir)) != NULL)	/* loop until last dir entry */
3488   {
3489     struct stat file_status;
3490     char *directory_name = dir_entry->d_name;
3491     char *directory_path = getPath2(base_directory, directory_name);
3492 
3493     /* skip directory entries for current and parent directory */
3494     if (strEqual(directory_name, ".") ||
3495 	strEqual(directory_name, ".."))
3496     {
3497       free(directory_path);
3498       continue;
3499     }
3500 
3501     /* skip directory entries which are not a directory or are not accessible */
3502     if (stat(directory_path, &file_status) != 0 ||	/* cannot stat file */
3503 	(file_status.st_mode & S_IFMT) != S_IFDIR)	/* not a directory */
3504     {
3505       free(directory_path);
3506       continue;
3507     }
3508 
3509     free(directory_path);
3510 
3511     /* check if this directory contains artwork with or without config file */
3512     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3513 							base_directory,
3514 							directory_name, type);
3515   }
3516 
3517   closedir(dir);
3518 
3519   /* check if this directory directly contains artwork itself */
3520   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3521 						      base_directory, ".",
3522 						      type);
3523   if (!valid_entry_found)
3524     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3525 	  base_directory);
3526 }
3527 
getDummyArtworkInfo(int type)3528 static TreeInfo *getDummyArtworkInfo(int type)
3529 {
3530   /* this is only needed when there is completely no artwork available */
3531   TreeInfo *artwork_new = newTreeInfo();
3532 
3533   setTreeInfoToDefaults(artwork_new, type);
3534 
3535   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3536   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3537   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3538 
3539   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3540   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3541   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3542 
3543   return artwork_new;
3544 }
3545 
LoadArtworkInfo()3546 void LoadArtworkInfo()
3547 {
3548   LoadArtworkInfoCache();
3549 
3550   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3551 
3552   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3553 				options.graphics_directory,
3554 				TREE_TYPE_GRAPHICS_DIR);
3555   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3556 				getUserGraphicsDir(),
3557 				TREE_TYPE_GRAPHICS_DIR);
3558 
3559   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3560 				options.sounds_directory,
3561 				TREE_TYPE_SOUNDS_DIR);
3562   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3563 				getUserSoundsDir(),
3564 				TREE_TYPE_SOUNDS_DIR);
3565 
3566   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3567 				options.music_directory,
3568 				TREE_TYPE_MUSIC_DIR);
3569   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3570 				getUserMusicDir(),
3571 				TREE_TYPE_MUSIC_DIR);
3572 
3573   if (artwork.gfx_first == NULL)
3574     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3575   if (artwork.snd_first == NULL)
3576     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3577   if (artwork.mus_first == NULL)
3578     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3579 
3580   /* before sorting, the first entries will be from the user directory */
3581   artwork.gfx_current =
3582     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3583   if (artwork.gfx_current == NULL)
3584     artwork.gfx_current =
3585       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3586   if (artwork.gfx_current == NULL)
3587     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3588 
3589   artwork.snd_current =
3590     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3591   if (artwork.snd_current == NULL)
3592     artwork.snd_current =
3593       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3594   if (artwork.snd_current == NULL)
3595     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3596 
3597   artwork.mus_current =
3598     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3599   if (artwork.mus_current == NULL)
3600     artwork.mus_current =
3601       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3602   if (artwork.mus_current == NULL)
3603     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3604 
3605   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3606   artwork.snd_current_identifier = artwork.snd_current->identifier;
3607   artwork.mus_current_identifier = artwork.mus_current->identifier;
3608 
3609 #if 0
3610   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3611   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3612   printf("music set == %s\n\n", artwork.mus_current_identifier);
3613 #endif
3614 
3615   sortTreeInfo(&artwork.gfx_first);
3616   sortTreeInfo(&artwork.snd_first);
3617   sortTreeInfo(&artwork.mus_first);
3618 
3619 #if 0
3620   dumpTreeInfo(artwork.gfx_first, 0);
3621   dumpTreeInfo(artwork.snd_first, 0);
3622   dumpTreeInfo(artwork.mus_first, 0);
3623 #endif
3624 }
3625 
LoadArtworkInfoFromLevelInfo(ArtworkDirTree ** artwork_node,LevelDirTree * level_node)3626 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3627 				  LevelDirTree *level_node)
3628 {
3629 #if 0
3630   static unsigned int progress_delay = 0;
3631   unsigned int progress_delay_value = 100;	/* (in milliseconds) */
3632 #endif
3633   int type = (*artwork_node)->type;
3634 
3635   /* recursively check all level directories for artwork sub-directories */
3636 
3637   while (level_node)
3638   {
3639     /* check all tree entries for artwork, but skip parent link entries */
3640     if (!level_node->parent_link)
3641     {
3642       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3643       boolean cached = (artwork_new != NULL);
3644 
3645       if (cached)
3646       {
3647 	pushTreeInfo(artwork_node, artwork_new);
3648       }
3649       else
3650       {
3651 	TreeInfo *topnode_last = *artwork_node;
3652 	char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3653 			      ARTWORK_DIRECTORY(type));
3654 
3655 	LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3656 
3657 	if (topnode_last != *artwork_node)	/* check for newly added node */
3658 	{
3659 	  artwork_new = *artwork_node;
3660 
3661 	  setString(&artwork_new->identifier,   level_node->subdir);
3662 	  setString(&artwork_new->name,         level_node->name);
3663 	  setString(&artwork_new->name_sorting, level_node->name_sorting);
3664 
3665 	  artwork_new->sort_priority = level_node->sort_priority;
3666 	  artwork_new->color = LEVELCOLOR(artwork_new);
3667 	}
3668 
3669 	free(path);
3670       }
3671 
3672       /* insert artwork info (from old cache or filesystem) into new cache */
3673       if (artwork_new != NULL)
3674 	setArtworkInfoCacheEntry(artwork_new, level_node, type);
3675     }
3676 
3677 #if 1
3678     DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3679 		    level_node->level_group);
3680 #else
3681     if (level_node->level_group ||
3682 	DelayReached(&progress_delay, progress_delay_value))
3683       DrawInitText(level_node->name, 150, FC_YELLOW);
3684 #endif
3685 
3686     if (level_node->node_group != NULL)
3687       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3688 
3689     level_node = level_node->next;
3690   }
3691 }
3692 
LoadLevelArtworkInfo()3693 void LoadLevelArtworkInfo()
3694 {
3695   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3696 
3697   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3698   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3699   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3700 
3701   SaveArtworkInfoCache();
3702 
3703   /* needed for reloading level artwork not known at ealier stage */
3704 
3705   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3706   {
3707     artwork.gfx_current =
3708       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3709     if (artwork.gfx_current == NULL)
3710       artwork.gfx_current =
3711 	getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3712     if (artwork.gfx_current == NULL)
3713       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3714   }
3715 
3716   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3717   {
3718     artwork.snd_current =
3719       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3720     if (artwork.snd_current == NULL)
3721       artwork.snd_current =
3722 	getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3723     if (artwork.snd_current == NULL)
3724       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3725   }
3726 
3727   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3728   {
3729     artwork.mus_current =
3730       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3731     if (artwork.mus_current == NULL)
3732       artwork.mus_current =
3733 	getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3734     if (artwork.mus_current == NULL)
3735       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3736   }
3737 
3738   sortTreeInfo(&artwork.gfx_first);
3739   sortTreeInfo(&artwork.snd_first);
3740   sortTreeInfo(&artwork.mus_first);
3741 
3742 #if 0
3743   dumpTreeInfo(artwork.gfx_first, 0);
3744   dumpTreeInfo(artwork.snd_first, 0);
3745   dumpTreeInfo(artwork.mus_first, 0);
3746 #endif
3747 }
3748 
SaveUserLevelInfo()3749 static void SaveUserLevelInfo()
3750 {
3751   LevelDirTree *level_info;
3752   char *filename;
3753   FILE *file;
3754   int i;
3755 
3756   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3757 
3758   if (!(file = fopen(filename, MODE_WRITE)))
3759   {
3760     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3761     free(filename);
3762     return;
3763   }
3764 
3765   level_info = newTreeInfo();
3766 
3767   /* always start with reliable default values */
3768   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3769 
3770   setString(&level_info->name, getLoginName());
3771   setString(&level_info->author, getRealName());
3772   level_info->levels = 100;
3773   level_info->first_level = 1;
3774 
3775   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3776 
3777   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3778 						 getCookie("LEVELINFO")));
3779 
3780   ldi = *level_info;
3781   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3782   {
3783     if (i == LEVELINFO_TOKEN_NAME ||
3784 	i == LEVELINFO_TOKEN_AUTHOR ||
3785 	i == LEVELINFO_TOKEN_LEVELS ||
3786 	i == LEVELINFO_TOKEN_FIRST_LEVEL)
3787       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3788 
3789     /* just to make things nicer :) */
3790     if (i == LEVELINFO_TOKEN_AUTHOR)
3791       fprintf(file, "\n");
3792   }
3793 
3794   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3795 
3796   fclose(file);
3797 
3798   SetFilePermissions(filename, PERMS_PRIVATE);
3799 
3800   freeTreeInfo(level_info);
3801   free(filename);
3802 }
3803 
getSetupValue(int type,void * value)3804 char *getSetupValue(int type, void *value)
3805 {
3806   static char value_string[MAX_LINE_LEN];
3807 
3808   if (value == NULL)
3809     return NULL;
3810 
3811   switch (type)
3812   {
3813     case TYPE_BOOLEAN:
3814       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3815       break;
3816 
3817     case TYPE_SWITCH:
3818       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3819       break;
3820 
3821     case TYPE_SWITCH3:
3822       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3823 			    *(int *)value == FALSE ? "off" : "on"));
3824       break;
3825 
3826     case TYPE_YES_NO:
3827       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3828       break;
3829 
3830     case TYPE_YES_NO_AUTO:
3831       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3832 			    *(int *)value == FALSE ? "no" : "yes"));
3833       break;
3834 
3835     case TYPE_ECS_AGA:
3836       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3837       break;
3838 
3839     case TYPE_KEY:
3840       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3841       break;
3842 
3843     case TYPE_KEY_X11:
3844       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3845       break;
3846 
3847     case TYPE_INTEGER:
3848       sprintf(value_string, "%d", *(int *)value);
3849       break;
3850 
3851     case TYPE_STRING:
3852       if (*(char **)value == NULL)
3853 	return NULL;
3854 
3855       strcpy(value_string, *(char **)value);
3856       break;
3857 
3858     default:
3859       value_string[0] = '\0';
3860       break;
3861   }
3862 
3863   if (type & TYPE_GHOSTED)
3864     strcpy(value_string, "n/a");
3865 
3866   return value_string;
3867 }
3868 
getSetupLine(struct TokenInfo * token_info,char * prefix,int token_nr)3869 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3870 {
3871   int i;
3872   char *line;
3873   static char token_string[MAX_LINE_LEN];
3874   int token_type = token_info[token_nr].type;
3875   void *setup_value = token_info[token_nr].value;
3876   char *token_text = token_info[token_nr].text;
3877   char *value_string = getSetupValue(token_type, setup_value);
3878 
3879   /* build complete token string */
3880   sprintf(token_string, "%s%s", prefix, token_text);
3881 
3882   /* build setup entry line */
3883   line = getFormattedSetupEntry(token_string, value_string);
3884 
3885   if (token_type == TYPE_KEY_X11)
3886   {
3887     Key key = *(Key *)setup_value;
3888     char *keyname = getKeyNameFromKey(key);
3889 
3890     /* add comment, if useful */
3891     if (!strEqual(keyname, "(undefined)") &&
3892 	!strEqual(keyname, "(unknown)"))
3893     {
3894       /* add at least one whitespace */
3895       strcat(line, " ");
3896       for (i = strlen(line); i < token_comment_position; i++)
3897 	strcat(line, " ");
3898 
3899       strcat(line, "# ");
3900       strcat(line, keyname);
3901     }
3902   }
3903 
3904   return line;
3905 }
3906 
LoadLevelSetup_LastSeries()3907 void LoadLevelSetup_LastSeries()
3908 {
3909   /* ----------------------------------------------------------------------- */
3910   /* ~/.<program>/levelsetup.conf                                            */
3911   /* ----------------------------------------------------------------------- */
3912 
3913   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3914   SetupFileHash *level_setup_hash = NULL;
3915 
3916   /* always start with reliable default values */
3917   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3918 
3919 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3920   leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3921 					       "jue_start");
3922   if (leveldir_current == NULL)
3923     leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3924 #endif
3925 
3926   if ((level_setup_hash = loadSetupFileHash(filename)))
3927   {
3928     char *last_level_series =
3929       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3930 
3931     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3932 						 last_level_series);
3933     if (leveldir_current == NULL)
3934       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3935 
3936     checkSetupFileHashIdentifier(level_setup_hash, filename,
3937 				 getCookie("LEVELSETUP"));
3938 
3939     freeSetupFileHash(level_setup_hash);
3940   }
3941   else
3942     Error(ERR_WARN, "using default setup values");
3943 
3944   free(filename);
3945 }
3946 
SaveLevelSetup_LastSeries()3947 void SaveLevelSetup_LastSeries()
3948 {
3949   /* ----------------------------------------------------------------------- */
3950   /* ~/.<program>/levelsetup.conf                                            */
3951   /* ----------------------------------------------------------------------- */
3952 
3953   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3954   char *level_subdir = leveldir_current->subdir;
3955   FILE *file;
3956 
3957   InitUserDataDirectory();
3958 
3959   if (!(file = fopen(filename, MODE_WRITE)))
3960   {
3961     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3962     free(filename);
3963     return;
3964   }
3965 
3966   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3967 						 getCookie("LEVELSETUP")));
3968   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3969 					       level_subdir));
3970 
3971   fclose(file);
3972 
3973   SetFilePermissions(filename, PERMS_PRIVATE);
3974 
3975   free(filename);
3976 }
3977 
checkSeriesInfo()3978 static void checkSeriesInfo()
3979 {
3980   static char *level_directory = NULL;
3981   DIR *dir;
3982 #if 0
3983   struct dirent *dir_entry;
3984 #endif
3985 
3986   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3987 
3988   level_directory = getPath2((leveldir_current->in_user_dir ?
3989 			      getUserLevelDir(NULL) :
3990 			      options.level_directory),
3991 			     leveldir_current->fullpath);
3992 
3993   if ((dir = opendir(level_directory)) == NULL)
3994   {
3995     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3996 
3997     return;
3998   }
3999 
4000 #if 0
4001   while ((dir_entry = readdir(dir)) != NULL)	/* last directory entry */
4002   {
4003     if (strlen(dir_entry->d_name) > 4 &&
4004 	dir_entry->d_name[3] == '.' &&
4005 	strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4006     {
4007       char levelnum_str[4];
4008       int levelnum_value;
4009 
4010       strncpy(levelnum_str, dir_entry->d_name, 3);
4011       levelnum_str[3] = '\0';
4012 
4013       levelnum_value = atoi(levelnum_str);
4014 
4015       if (levelnum_value < leveldir_current->first_level)
4016       {
4017 	Error(ERR_WARN, "additional level %d found", levelnum_value);
4018 	leveldir_current->first_level = levelnum_value;
4019       }
4020       else if (levelnum_value > leveldir_current->last_level)
4021       {
4022 	Error(ERR_WARN, "additional level %d found", levelnum_value);
4023 	leveldir_current->last_level = levelnum_value;
4024       }
4025     }
4026   }
4027 #endif
4028 
4029   closedir(dir);
4030 }
4031 
LoadLevelSetup_SeriesInfo()4032 void LoadLevelSetup_SeriesInfo()
4033 {
4034   char *filename;
4035   SetupFileHash *level_setup_hash = NULL;
4036   char *level_subdir = leveldir_current->subdir;
4037   int i;
4038 
4039   /* always start with reliable default values */
4040   level_nr = leveldir_current->first_level;
4041 
4042   for (i = 0; i < MAX_LEVELS; i++)
4043   {
4044     LevelStats_setPlayed(i, 0);
4045     LevelStats_setSolved(i, 0);
4046   }
4047 
4048   checkSeriesInfo(leveldir_current);
4049 
4050   /* ----------------------------------------------------------------------- */
4051   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4052   /* ----------------------------------------------------------------------- */
4053 
4054   level_subdir = leveldir_current->subdir;
4055 
4056   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4057 
4058   if ((level_setup_hash = loadSetupFileHash(filename)))
4059   {
4060     char *token_value;
4061 
4062     /* get last played level in this level set */
4063 
4064     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4065 
4066     if (token_value)
4067     {
4068       level_nr = atoi(token_value);
4069 
4070       if (level_nr < leveldir_current->first_level)
4071 	level_nr = leveldir_current->first_level;
4072       if (level_nr > leveldir_current->last_level)
4073 	level_nr = leveldir_current->last_level;
4074     }
4075 
4076     /* get handicap level in this level set */
4077 
4078     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4079 
4080     if (token_value)
4081     {
4082       int level_nr = atoi(token_value);
4083 
4084       if (level_nr < leveldir_current->first_level)
4085 	level_nr = leveldir_current->first_level;
4086       if (level_nr > leveldir_current->last_level + 1)
4087 	level_nr = leveldir_current->last_level;
4088 
4089       if (leveldir_current->user_defined || !leveldir_current->handicap)
4090 	level_nr = leveldir_current->last_level;
4091 
4092       leveldir_current->handicap_level = level_nr;
4093     }
4094 
4095     /* get number of played and solved levels in this level set */
4096 
4097     BEGIN_HASH_ITERATION(level_setup_hash, itr)
4098     {
4099       char *token = HASH_ITERATION_TOKEN(itr);
4100       char *value = HASH_ITERATION_VALUE(itr);
4101 
4102       if (strlen(token) == 3 &&
4103 	  token[0] >= '0' && token[0] <= '9' &&
4104 	  token[1] >= '0' && token[1] <= '9' &&
4105 	  token[2] >= '0' && token[2] <= '9')
4106       {
4107 	int level_nr = atoi(token);
4108 
4109 	if (value != NULL)
4110 	  LevelStats_setPlayed(level_nr, atoi(value));	/* read 1st column */
4111 
4112 	value = strchr(value, ' ');
4113 
4114 	if (value != NULL)
4115 	  LevelStats_setSolved(level_nr, atoi(value));	/* read 2nd column */
4116       }
4117     }
4118     END_HASH_ITERATION(hash, itr)
4119 
4120     checkSetupFileHashIdentifier(level_setup_hash, filename,
4121 				 getCookie("LEVELSETUP"));
4122 
4123     freeSetupFileHash(level_setup_hash);
4124   }
4125   else
4126     Error(ERR_WARN, "using default setup values");
4127 
4128   free(filename);
4129 }
4130 
SaveLevelSetup_SeriesInfo()4131 void SaveLevelSetup_SeriesInfo()
4132 {
4133   char *filename;
4134   char *level_subdir = leveldir_current->subdir;
4135   char *level_nr_str = int2str(level_nr, 0);
4136   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4137   FILE *file;
4138   int i;
4139 
4140   /* ----------------------------------------------------------------------- */
4141   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4142   /* ----------------------------------------------------------------------- */
4143 
4144   InitLevelSetupDirectory(level_subdir);
4145 
4146   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4147 
4148   if (!(file = fopen(filename, MODE_WRITE)))
4149   {
4150     Error(ERR_WARN, "cannot write setup file '%s'", filename);
4151     free(filename);
4152     return;
4153   }
4154 
4155   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4156 						 getCookie("LEVELSETUP")));
4157   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4158 					       level_nr_str));
4159   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4160 						 handicap_level_str));
4161 
4162   for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4163        i++)
4164   {
4165     if (LevelStats_getPlayed(i) > 0 ||
4166 	LevelStats_getSolved(i) > 0)
4167     {
4168       char token[16];
4169       char value[16];
4170 
4171       sprintf(token, "%03d", i);
4172       sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4173 
4174       fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4175     }
4176   }
4177 
4178   fclose(file);
4179 
4180   SetFilePermissions(filename, PERMS_PRIVATE);
4181 
4182   free(filename);
4183 }
4184 
LevelStats_getPlayed(int nr)4185 int LevelStats_getPlayed(int nr)
4186 {
4187   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4188 }
4189 
LevelStats_getSolved(int nr)4190 int LevelStats_getSolved(int nr)
4191 {
4192   return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4193 }
4194 
LevelStats_setPlayed(int nr,int value)4195 void LevelStats_setPlayed(int nr, int value)
4196 {
4197   if (nr >= 0 && nr < MAX_LEVELS)
4198     level_stats[nr].played = value;
4199 }
4200 
LevelStats_setSolved(int nr,int value)4201 void LevelStats_setSolved(int nr, int value)
4202 {
4203   if (nr >= 0 && nr < MAX_LEVELS)
4204     level_stats[nr].solved = value;
4205 }
4206 
LevelStats_incPlayed(int nr)4207 void LevelStats_incPlayed(int nr)
4208 {
4209   if (nr >= 0 && nr < MAX_LEVELS)
4210     level_stats[nr].played++;
4211 }
4212 
LevelStats_incSolved(int nr)4213 void LevelStats_incSolved(int nr)
4214 {
4215   if (nr >= 0 && nr < MAX_LEVELS)
4216     level_stats[nr].solved++;
4217 }
4218