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