1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
33 #define NUM_LEVELCLASS_DESC 8
35 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
48 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
49 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
50 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
51 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
56 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
59 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
60 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
61 IS_LEVELCLASS_BD(n) ? 2 : \
62 IS_LEVELCLASS_EM(n) ? 3 : \
63 IS_LEVELCLASS_SP(n) ? 4 : \
64 IS_LEVELCLASS_DX(n) ? 5 : \
65 IS_LEVELCLASS_SB(n) ? 6 : \
66 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
67 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
70 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
71 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
72 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
73 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
76 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
77 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
78 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
79 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
82 #define TOKEN_VALUE_POSITION_SHORT 32
83 #define TOKEN_VALUE_POSITION_DEFAULT 40
84 #define TOKEN_COMMENT_POSITION_DEFAULT 60
86 #define MAX_COOKIE_LEN 256
89 static void setTreeInfoToDefaults(TreeInfo *, int);
90 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
91 static int compareTreeInfoEntries(const void *, const void *);
93 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
94 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
96 static SetupFileHash *artworkinfo_cache_old = NULL;
97 static SetupFileHash *artworkinfo_cache_new = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
101 /* ------------------------------------------------------------------------- */
103 /* ------------------------------------------------------------------------- */
105 static char *getLevelClassDescription(TreeInfo *ti)
107 int position = ti->sort_priority / 100;
109 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
110 return levelclass_desc[position];
112 return "Unknown Level Class";
115 static char *getUserLevelDir(char *level_subdir)
117 static char *userlevel_dir = NULL;
118 char *data_dir = getUserGameDataDir();
119 char *userlevel_subdir = LEVELS_DIRECTORY;
121 checked_free(userlevel_dir);
123 if (level_subdir != NULL)
124 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
126 userlevel_dir = getPath2(data_dir, userlevel_subdir);
128 return userlevel_dir;
131 static char *getScoreDir(char *level_subdir)
133 static char *score_dir = NULL;
134 char *data_dir = getCommonDataDir();
135 char *score_subdir = SCORES_DIRECTORY;
137 checked_free(score_dir);
139 if (level_subdir != NULL)
140 score_dir = getPath3(data_dir, score_subdir, level_subdir);
142 score_dir = getPath2(data_dir, score_subdir);
147 static char *getLevelSetupDir(char *level_subdir)
149 static char *levelsetup_dir = NULL;
150 char *data_dir = getUserGameDataDir();
151 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
153 checked_free(levelsetup_dir);
155 if (level_subdir != NULL)
156 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
158 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
160 return levelsetup_dir;
163 static char *getCacheDir()
165 static char *cache_dir = NULL;
167 if (cache_dir == NULL)
168 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
173 static char *getLevelDirFromTreeInfo(TreeInfo *node)
175 static char *level_dir = NULL;
178 return options.level_directory;
180 checked_free(level_dir);
182 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
183 options.level_directory), node->fullpath);
188 char *getCurrentLevelDir()
190 return getLevelDirFromTreeInfo(leveldir_current);
193 static char *getTapeDir(char *level_subdir)
195 static char *tape_dir = NULL;
196 char *data_dir = getUserGameDataDir();
197 char *tape_subdir = TAPES_DIRECTORY;
199 checked_free(tape_dir);
201 if (level_subdir != NULL)
202 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
204 tape_dir = getPath2(data_dir, tape_subdir);
209 static char *getSolutionTapeDir()
211 static char *tape_dir = NULL;
212 char *data_dir = getCurrentLevelDir();
213 char *tape_subdir = TAPES_DIRECTORY;
215 checked_free(tape_dir);
217 tape_dir = getPath2(data_dir, tape_subdir);
222 static char *getDefaultGraphicsDir(char *graphics_subdir)
224 static char *graphics_dir = NULL;
226 if (graphics_subdir == NULL)
227 return options.graphics_directory;
229 checked_free(graphics_dir);
231 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
236 static char *getDefaultSoundsDir(char *sounds_subdir)
238 static char *sounds_dir = NULL;
240 if (sounds_subdir == NULL)
241 return options.sounds_directory;
243 checked_free(sounds_dir);
245 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
250 static char *getDefaultMusicDir(char *music_subdir)
252 static char *music_dir = NULL;
254 if (music_subdir == NULL)
255 return options.music_directory;
257 checked_free(music_dir);
259 music_dir = getPath2(options.music_directory, music_subdir);
264 static char *getClassicArtworkSet(int type)
266 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
267 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
268 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
271 static char *getClassicArtworkDir(int type)
273 return (type == TREE_TYPE_GRAPHICS_DIR ?
274 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
275 type == TREE_TYPE_SOUNDS_DIR ?
276 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_MUSIC_DIR ?
278 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
281 static char *getUserGraphicsDir()
283 static char *usergraphics_dir = NULL;
285 if (usergraphics_dir == NULL)
286 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
288 return usergraphics_dir;
291 static char *getUserSoundsDir()
293 static char *usersounds_dir = NULL;
295 if (usersounds_dir == NULL)
296 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
298 return usersounds_dir;
301 static char *getUserMusicDir()
303 static char *usermusic_dir = NULL;
305 if (usermusic_dir == NULL)
306 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
308 return usermusic_dir;
311 static char *getSetupArtworkDir(TreeInfo *ti)
313 static char *artwork_dir = NULL;
315 checked_free(artwork_dir);
317 artwork_dir = getPath2(ti->basepath, ti->fullpath);
322 char *setLevelArtworkDir(TreeInfo *ti)
324 char **artwork_path_ptr, **artwork_set_ptr;
325 TreeInfo *level_artwork;
327 if (ti == NULL || leveldir_current == NULL)
330 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
331 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
333 checked_free(*artwork_path_ptr);
335 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
342 No (or non-existing) artwork configured in "levelinfo.conf". This would
343 normally result in using the artwork configured in the setup menu. But
344 if an artwork subdirectory exists (which might contain custom artwork
345 or an artwork configuration file), this level artwork must be treated
346 as relative to the default "classic" artwork, not to the artwork that
347 is currently configured in the setup menu.
349 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
350 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
351 the real "classic" artwork from the original R'n'D (like "gfx_classic").
354 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
356 checked_free(*artwork_set_ptr);
358 if (directoryExists(dir))
360 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
361 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
365 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
366 *artwork_set_ptr = NULL;
372 return *artwork_set_ptr;
375 inline static char *getLevelArtworkSet(int type)
377 if (leveldir_current == NULL)
380 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
383 inline static char *getLevelArtworkDir(int type)
385 if (leveldir_current == NULL)
386 return UNDEFINED_FILENAME;
388 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
391 char *getTapeFilename(int nr)
393 static char *filename = NULL;
394 char basename[MAX_FILENAME_LEN];
396 checked_free(filename);
398 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
399 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
404 char *getSolutionTapeFilename(int nr)
406 static char *filename = NULL;
407 char basename[MAX_FILENAME_LEN];
409 checked_free(filename);
411 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
412 filename = getPath2(getSolutionTapeDir(), basename);
414 if (!fileExists(filename))
416 static char *filename_sln = NULL;
418 checked_free(filename_sln);
420 sprintf(basename, "%03d.sln", nr);
421 filename_sln = getPath2(getSolutionTapeDir(), basename);
423 if (fileExists(filename_sln))
430 char *getScoreFilename(int nr)
432 static char *filename = NULL;
433 char basename[MAX_FILENAME_LEN];
435 checked_free(filename);
437 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
438 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
443 char *getSetupFilename()
445 static char *filename = NULL;
447 checked_free(filename);
449 filename = getPath2(getSetupDir(), SETUP_FILENAME);
454 char *getEditorSetupFilename()
456 static char *filename = NULL;
458 checked_free(filename);
459 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
461 if (fileExists(filename))
464 checked_free(filename);
465 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
470 char *getHelpAnimFilename()
472 static char *filename = NULL;
474 checked_free(filename);
476 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
481 char *getHelpTextFilename()
483 static char *filename = NULL;
485 checked_free(filename);
487 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
492 char *getLevelSetInfoFilename()
494 static char *filename = NULL;
509 for (i = 0; basenames[i] != NULL; i++)
511 checked_free(filename);
512 filename = getPath2(getCurrentLevelDir(), basenames[i]);
514 if (fileExists(filename))
521 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
523 static char basename[32];
525 sprintf(basename, "%s_%d.txt",
526 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
531 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
533 static char *filename = NULL;
535 boolean skip_setup_artwork = FALSE;
537 checked_free(filename);
539 basename = getLevelSetTitleMessageBasename(nr, initial);
541 if (!gfx.override_level_graphics)
543 /* 1st try: look for special artwork in current level series directory */
544 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
545 if (fileExists(filename))
550 /* 2nd try: look for message file in current level set directory */
551 filename = getPath2(getCurrentLevelDir(), basename);
552 if (fileExists(filename))
557 /* check if there is special artwork configured in level series config */
558 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
560 /* 3rd try: look for special artwork configured in level series config */
561 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
562 if (fileExists(filename))
567 /* take missing artwork configured in level set config from default */
568 skip_setup_artwork = TRUE;
572 if (!skip_setup_artwork)
574 /* 4th try: look for special artwork in configured artwork directory */
575 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
576 if (fileExists(filename))
582 /* 5th try: look for default artwork in new default artwork directory */
583 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
584 if (fileExists(filename))
589 /* 6th try: look for default artwork in old default artwork directory */
590 filename = getPath2(options.graphics_directory, basename);
591 if (fileExists(filename))
594 return NULL; /* cannot find specified artwork file anywhere */
597 static char *getCorrectedArtworkBasename(char *basename)
602 char *getCustomImageFilename(char *basename)
604 static char *filename = NULL;
605 boolean skip_setup_artwork = FALSE;
607 checked_free(filename);
609 basename = getCorrectedArtworkBasename(basename);
611 if (!gfx.override_level_graphics)
613 /* 1st try: look for special artwork in current level series directory */
614 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
615 if (fileExists(filename))
620 /* check if there is special artwork configured in level series config */
621 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
623 /* 2nd try: look for special artwork configured in level series config */
624 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
625 if (fileExists(filename))
630 /* take missing artwork configured in level set config from default */
631 skip_setup_artwork = TRUE;
635 if (!skip_setup_artwork)
637 /* 3rd try: look for special artwork in configured artwork directory */
638 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
639 if (fileExists(filename))
645 /* 4th try: look for default artwork in new default artwork directory */
646 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
647 if (fileExists(filename))
652 /* 5th try: look for default artwork in old default artwork directory */
653 filename = getPath2(options.graphics_directory, basename);
654 if (fileExists(filename))
657 #if defined(CREATE_SPECIAL_EDITION)
661 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
663 /* 6th try: look for fallback artwork in old default artwork directory */
664 /* (needed to prevent errors when trying to access unused artwork files) */
665 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
666 if (fileExists(filename))
670 return NULL; /* cannot find specified artwork file anywhere */
673 char *getCustomSoundFilename(char *basename)
675 static char *filename = NULL;
676 boolean skip_setup_artwork = FALSE;
678 checked_free(filename);
680 basename = getCorrectedArtworkBasename(basename);
682 if (!gfx.override_level_sounds)
684 /* 1st try: look for special artwork in current level series directory */
685 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
686 if (fileExists(filename))
691 /* check if there is special artwork configured in level series config */
692 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
694 /* 2nd try: look for special artwork configured in level series config */
695 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
696 if (fileExists(filename))
701 /* take missing artwork configured in level set config from default */
702 skip_setup_artwork = TRUE;
706 if (!skip_setup_artwork)
708 /* 3rd try: look for special artwork in configured artwork directory */
709 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
710 if (fileExists(filename))
716 /* 4th try: look for default artwork in new default artwork directory */
717 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
718 if (fileExists(filename))
723 /* 5th try: look for default artwork in old default artwork directory */
724 filename = getPath2(options.sounds_directory, basename);
725 if (fileExists(filename))
728 #if defined(CREATE_SPECIAL_EDITION)
732 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
734 /* 6th try: look for fallback artwork in old default artwork directory */
735 /* (needed to prevent errors when trying to access unused artwork files) */
736 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
737 if (fileExists(filename))
741 return NULL; /* cannot find specified artwork file anywhere */
744 char *getCustomMusicFilename(char *basename)
746 static char *filename = NULL;
747 boolean skip_setup_artwork = FALSE;
749 checked_free(filename);
751 basename = getCorrectedArtworkBasename(basename);
753 if (!gfx.override_level_music)
755 /* 1st try: look for special artwork in current level series directory */
756 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
757 if (fileExists(filename))
762 /* check if there is special artwork configured in level series config */
763 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
765 /* 2nd try: look for special artwork configured in level series config */
766 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
767 if (fileExists(filename))
772 /* take missing artwork configured in level set config from default */
773 skip_setup_artwork = TRUE;
777 if (!skip_setup_artwork)
779 /* 3rd try: look for special artwork in configured artwork directory */
780 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
781 if (fileExists(filename))
787 /* 4th try: look for default artwork in new default artwork directory */
788 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
789 if (fileExists(filename))
794 /* 5th try: look for default artwork in old default artwork directory */
795 filename = getPath2(options.music_directory, basename);
796 if (fileExists(filename))
799 #if defined(CREATE_SPECIAL_EDITION)
803 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
805 /* 6th try: look for fallback artwork in old default artwork directory */
806 /* (needed to prevent errors when trying to access unused artwork files) */
807 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
808 if (fileExists(filename))
812 return NULL; /* cannot find specified artwork file anywhere */
815 char *getCustomArtworkFilename(char *basename, int type)
817 if (type == ARTWORK_TYPE_GRAPHICS)
818 return getCustomImageFilename(basename);
819 else if (type == ARTWORK_TYPE_SOUNDS)
820 return getCustomSoundFilename(basename);
821 else if (type == ARTWORK_TYPE_MUSIC)
822 return getCustomMusicFilename(basename);
824 return UNDEFINED_FILENAME;
827 char *getCustomArtworkConfigFilename(int type)
829 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
832 char *getCustomArtworkLevelConfigFilename(int type)
834 static char *filename = NULL;
836 checked_free(filename);
838 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
843 char *getCustomMusicDirectory(void)
845 static char *directory = NULL;
846 boolean skip_setup_artwork = FALSE;
848 checked_free(directory);
850 if (!gfx.override_level_music)
852 /* 1st try: look for special artwork in current level series directory */
853 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
854 if (directoryExists(directory))
859 /* check if there is special artwork configured in level series config */
860 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
862 /* 2nd try: look for special artwork configured in level series config */
863 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
864 if (directoryExists(directory))
869 /* take missing artwork configured in level set config from default */
870 skip_setup_artwork = TRUE;
874 if (!skip_setup_artwork)
876 /* 3rd try: look for special artwork in configured artwork directory */
877 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
878 if (directoryExists(directory))
884 /* 4th try: look for default artwork in new default artwork directory */
885 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
886 if (directoryExists(directory))
891 /* 5th try: look for default artwork in old default artwork directory */
892 directory = getStringCopy(options.music_directory);
893 if (directoryExists(directory))
896 return NULL; /* cannot find specified artwork file anywhere */
899 void InitTapeDirectory(char *level_subdir)
901 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
902 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
903 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
906 void InitScoreDirectory(char *level_subdir)
908 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
909 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
910 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
913 static void SaveUserLevelInfo();
915 void InitUserLevelDirectory(char *level_subdir)
917 if (!directoryExists(getUserLevelDir(level_subdir)))
919 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
920 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
921 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
927 void InitLevelSetupDirectory(char *level_subdir)
929 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
930 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
931 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
934 void InitCacheDirectory()
936 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
937 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
941 /* ------------------------------------------------------------------------- */
942 /* some functions to handle lists of level and artwork directories */
943 /* ------------------------------------------------------------------------- */
945 TreeInfo *newTreeInfo()
947 return checked_calloc(sizeof(TreeInfo));
950 TreeInfo *newTreeInfo_setDefaults(int type)
952 TreeInfo *ti = newTreeInfo();
954 setTreeInfoToDefaults(ti, type);
959 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
961 node_new->next = *node_first;
962 *node_first = node_new;
965 int numTreeInfo(TreeInfo *node)
978 boolean validLevelSeries(TreeInfo *node)
980 return (node != NULL && !node->node_group && !node->parent_link);
983 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
988 if (node->node_group) /* enter level group (step down into tree) */
989 return getFirstValidTreeInfoEntry(node->node_group);
990 else if (node->parent_link) /* skip start entry of level group */
992 if (node->next) /* get first real level series entry */
993 return getFirstValidTreeInfoEntry(node->next);
994 else /* leave empty level group and go on */
995 return getFirstValidTreeInfoEntry(node->node_parent->next);
997 else /* this seems to be a regular level series */
1001 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1006 if (node->node_parent == NULL) /* top level group */
1007 return *node->node_top;
1008 else /* sub level group */
1009 return node->node_parent->node_group;
1012 int numTreeInfoInGroup(TreeInfo *node)
1014 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1017 int posTreeInfo(TreeInfo *node)
1019 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1024 if (node_cmp == node)
1028 node_cmp = node_cmp->next;
1034 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1036 TreeInfo *node_default = node;
1048 return node_default;
1051 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1053 if (identifier == NULL)
1058 if (node->node_group)
1060 TreeInfo *node_group;
1062 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1067 else if (!node->parent_link)
1069 if (strEqual(identifier, node->identifier))
1079 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1080 TreeInfo *node, boolean skip_sets_without_levels)
1087 if (!node->parent_link && !node->level_group &&
1088 skip_sets_without_levels && node->levels == 0)
1089 return cloneTreeNode(node_top, node_parent, node->next,
1090 skip_sets_without_levels);
1093 node_new = getTreeInfoCopy(node); /* copy complete node */
1095 node_new = newTreeInfo();
1097 *node_new = *node; /* copy complete node */
1100 node_new->node_top = node_top; /* correct top node link */
1101 node_new->node_parent = node_parent; /* correct parent node link */
1103 if (node->level_group)
1104 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1105 skip_sets_without_levels);
1107 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1108 skip_sets_without_levels);
1113 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1115 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1117 *ti_new = ti_cloned;
1120 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1122 boolean settings_changed = FALSE;
1126 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1127 !strEqual(node->graphics_set, node->graphics_set_ecs))
1129 setString(&node->graphics_set, node->graphics_set_ecs);
1130 settings_changed = TRUE;
1132 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1133 !strEqual(node->graphics_set, node->graphics_set_aga))
1135 setString(&node->graphics_set, node->graphics_set_aga);
1136 settings_changed = TRUE;
1139 if (node->node_group != NULL)
1140 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1145 return settings_changed;
1148 void dumpTreeInfo(TreeInfo *node, int depth)
1152 printf("Dumping TreeInfo:\n");
1156 for (i = 0; i < (depth + 1) * 3; i++)
1159 printf("'%s' / '%s'\n", node->identifier, node->name);
1162 // use for dumping artwork info tree
1163 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1164 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1167 if (node->node_group != NULL)
1168 dumpTreeInfo(node->node_group, depth + 1);
1174 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1175 int (*compare_function)(const void *,
1178 int num_nodes = numTreeInfo(*node_first);
1179 TreeInfo **sort_array;
1180 TreeInfo *node = *node_first;
1186 /* allocate array for sorting structure pointers */
1187 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1189 /* writing structure pointers to sorting array */
1190 while (i < num_nodes && node) /* double boundary check... */
1192 sort_array[i] = node;
1198 /* sorting the structure pointers in the sorting array */
1199 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1202 /* update the linkage of list elements with the sorted node array */
1203 for (i = 0; i < num_nodes - 1; i++)
1204 sort_array[i]->next = sort_array[i + 1];
1205 sort_array[num_nodes - 1]->next = NULL;
1207 /* update the linkage of the main list anchor pointer */
1208 *node_first = sort_array[0];
1212 /* now recursively sort the level group structures */
1216 if (node->node_group != NULL)
1217 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1223 void sortTreeInfo(TreeInfo **node_first)
1225 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1229 /* ========================================================================= */
1230 /* some stuff from "files.c" */
1231 /* ========================================================================= */
1233 #if defined(PLATFORM_WIN32)
1235 #define S_IRGRP S_IRUSR
1238 #define S_IROTH S_IRUSR
1241 #define S_IWGRP S_IWUSR
1244 #define S_IWOTH S_IWUSR
1247 #define S_IXGRP S_IXUSR
1250 #define S_IXOTH S_IXUSR
1253 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1258 #endif /* PLATFORM_WIN32 */
1260 /* file permissions for newly written files */
1261 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1262 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1263 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1265 #define MODE_W_PRIVATE (S_IWUSR)
1266 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1267 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1269 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1270 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1272 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1273 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1277 static char *dir = NULL;
1279 #if defined(PLATFORM_WIN32)
1282 dir = checked_malloc(MAX_PATH + 1);
1284 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1287 #elif defined(PLATFORM_UNIX)
1290 if ((dir = getenv("HOME")) == NULL)
1294 if ((pwd = getpwuid(getuid())) != NULL)
1295 dir = getStringCopy(pwd->pw_dir);
1307 char *getCommonDataDir(void)
1309 static char *common_data_dir = NULL;
1311 #if defined(PLATFORM_WIN32)
1312 if (common_data_dir == NULL)
1314 char *dir = checked_malloc(MAX_PATH + 1);
1316 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1317 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1318 common_data_dir = getPath2(dir, program.userdata_subdir);
1320 common_data_dir = options.rw_base_directory;
1323 if (common_data_dir == NULL)
1324 common_data_dir = options.rw_base_directory;
1327 return common_data_dir;
1330 char *getPersonalDataDir(void)
1332 static char *personal_data_dir = NULL;
1334 #if defined(PLATFORM_MACOSX)
1335 if (personal_data_dir == NULL)
1336 personal_data_dir = getPath2(getHomeDir(), "Documents");
1338 if (personal_data_dir == NULL)
1339 personal_data_dir = getHomeDir();
1342 return personal_data_dir;
1345 char *getUserGameDataDir(void)
1347 static char *user_game_data_dir = NULL;
1349 #if defined(PLATFORM_ANDROID)
1350 if (user_game_data_dir == NULL)
1351 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1353 if (user_game_data_dir == NULL)
1354 user_game_data_dir = getPath2(getPersonalDataDir(),
1355 program.userdata_subdir);
1358 return user_game_data_dir;
1361 void updateUserGameDataDir()
1363 #if defined(PLATFORM_MACOSX)
1364 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1365 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1367 /* convert old Unix style game data directory to Mac OS X style, if needed */
1368 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1370 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1372 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1373 userdata_dir_old, userdata_dir_new);
1375 /* continue using Unix style data directory -- this should not happen */
1376 program.userdata_path = getPath2(getPersonalDataDir(),
1377 program.userdata_subdir_unix);
1381 free(userdata_dir_old);
1387 return getUserGameDataDir();
1390 static mode_t posix_umask(mode_t mask)
1392 #if defined(PLATFORM_UNIX)
1399 static int posix_mkdir(const char *pathname, mode_t mode)
1401 #if defined(PLATFORM_WIN32)
1402 return mkdir(pathname);
1404 return mkdir(pathname, mode);
1408 static boolean posix_process_running_setgid()
1410 #if defined(PLATFORM_UNIX)
1411 return (getgid() != getegid());
1417 void createDirectory(char *dir, char *text, int permission_class)
1419 /* leave "other" permissions in umask untouched, but ensure group parts
1420 of USERDATA_DIR_MODE are not masked */
1421 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1422 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1423 mode_t last_umask = posix_umask(0);
1424 mode_t group_umask = ~(dir_mode & S_IRWXG);
1425 int running_setgid = posix_process_running_setgid();
1427 /* if we're setgid, protect files against "other" */
1428 /* else keep umask(0) to make the dir world-writable */
1431 posix_umask(last_umask & group_umask);
1433 dir_mode |= MODE_W_ALL;
1435 if (!directoryExists(dir))
1436 if (posix_mkdir(dir, dir_mode) != 0)
1437 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1438 text, dir, strerror(errno));
1440 if (permission_class == PERMS_PUBLIC && !running_setgid)
1441 chmod(dir, dir_mode);
1443 posix_umask(last_umask); /* restore previous umask */
1446 void InitUserDataDirectory()
1448 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1451 void SetFilePermissions(char *filename, int permission_class)
1453 int running_setgid = posix_process_running_setgid();
1454 int perms = (permission_class == PERMS_PRIVATE ?
1455 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1457 if (permission_class == PERMS_PUBLIC && !running_setgid)
1458 perms |= MODE_W_ALL;
1460 chmod(filename, perms);
1463 char *getCookie(char *file_type)
1465 static char cookie[MAX_COOKIE_LEN + 1];
1467 if (strlen(program.cookie_prefix) + 1 +
1468 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1469 return "[COOKIE ERROR]"; /* should never happen */
1471 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1472 program.cookie_prefix, file_type,
1473 program.version_major, program.version_minor);
1478 int getFileVersionFromCookieString(const char *cookie)
1480 const char *ptr_cookie1, *ptr_cookie2;
1481 const char *pattern1 = "_FILE_VERSION_";
1482 const char *pattern2 = "?.?";
1483 const int len_cookie = strlen(cookie);
1484 const int len_pattern1 = strlen(pattern1);
1485 const int len_pattern2 = strlen(pattern2);
1486 const int len_pattern = len_pattern1 + len_pattern2;
1487 int version_major, version_minor;
1489 if (len_cookie <= len_pattern)
1492 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1493 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1495 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1498 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1499 ptr_cookie2[1] != '.' ||
1500 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1503 version_major = ptr_cookie2[0] - '0';
1504 version_minor = ptr_cookie2[2] - '0';
1506 return VERSION_IDENT(version_major, version_minor, 0, 0);
1509 boolean checkCookieString(const char *cookie, const char *template)
1511 const char *pattern = "_FILE_VERSION_?.?";
1512 const int len_cookie = strlen(cookie);
1513 const int len_template = strlen(template);
1514 const int len_pattern = strlen(pattern);
1516 if (len_cookie != len_template)
1519 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1525 /* ------------------------------------------------------------------------- */
1526 /* setup file list and hash handling functions */
1527 /* ------------------------------------------------------------------------- */
1529 char *getFormattedSetupEntry(char *token, char *value)
1532 static char entry[MAX_LINE_LEN];
1534 /* if value is an empty string, just return token without value */
1538 /* start with the token and some spaces to format output line */
1539 sprintf(entry, "%s:", token);
1540 for (i = strlen(entry); i < token_value_position; i++)
1543 /* continue with the token's value */
1544 strcat(entry, value);
1549 SetupFileList *newSetupFileList(char *token, char *value)
1551 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1553 new->token = getStringCopy(token);
1554 new->value = getStringCopy(value);
1561 void freeSetupFileList(SetupFileList *list)
1566 checked_free(list->token);
1567 checked_free(list->value);
1570 freeSetupFileList(list->next);
1575 char *getListEntry(SetupFileList *list, char *token)
1580 if (strEqual(list->token, token))
1583 return getListEntry(list->next, token);
1586 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1591 if (strEqual(list->token, token))
1593 checked_free(list->value);
1595 list->value = getStringCopy(value);
1599 else if (list->next == NULL)
1600 return (list->next = newSetupFileList(token, value));
1602 return setListEntry(list->next, token, value);
1605 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1610 if (list->next == NULL)
1611 return (list->next = newSetupFileList(token, value));
1613 return addListEntry(list->next, token, value);
1618 static void printSetupFileList(SetupFileList *list)
1623 printf("token: '%s'\n", list->token);
1624 printf("value: '%s'\n", list->value);
1626 printSetupFileList(list->next);
1632 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1633 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1634 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1635 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1637 #define insert_hash_entry hashtable_insert
1638 #define search_hash_entry hashtable_search
1639 #define change_hash_entry hashtable_change
1640 #define remove_hash_entry hashtable_remove
1643 unsigned int get_hash_from_key(void *key)
1648 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1649 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1650 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1651 it works better than many other constants, prime or not) has never been
1652 adequately explained.
1654 If you just want to have a good hash function, and cannot wait, djb2
1655 is one of the best string hash functions i know. It has excellent
1656 distribution and speed on many different sets of keys and table sizes.
1657 You are not likely to do better with one of the "well known" functions
1658 such as PJW, K&R, etc.
1660 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1663 char *str = (char *)key;
1664 unsigned int hash = 5381;
1667 while ((c = *str++))
1668 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1673 static int keys_are_equal(void *key1, void *key2)
1675 return (strEqual((char *)key1, (char *)key2));
1678 SetupFileHash *newSetupFileHash()
1680 SetupFileHash *new_hash =
1681 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1683 if (new_hash == NULL)
1684 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1689 void freeSetupFileHash(SetupFileHash *hash)
1694 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1697 char *getHashEntry(SetupFileHash *hash, char *token)
1702 return search_hash_entry(hash, token);
1705 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1712 value_copy = getStringCopy(value);
1714 /* change value; if it does not exist, insert it as new */
1715 if (!change_hash_entry(hash, token, value_copy))
1716 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1717 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1720 char *removeHashEntry(SetupFileHash *hash, char *token)
1725 return remove_hash_entry(hash, token);
1729 static void printSetupFileHash(SetupFileHash *hash)
1731 BEGIN_HASH_ITERATION(hash, itr)
1733 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1734 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1736 END_HASH_ITERATION(hash, itr)
1740 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1741 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1742 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1744 static boolean token_value_separator_found = FALSE;
1745 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1746 static boolean token_value_separator_warning = FALSE;
1748 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1749 static boolean token_already_exists_warning = FALSE;
1752 static boolean getTokenValueFromSetupLineExt(char *line,
1753 char **token_ptr, char **value_ptr,
1754 char *filename, char *line_raw,
1756 boolean separator_required)
1758 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1759 char *token, *value, *line_ptr;
1761 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1762 if (line_raw == NULL)
1764 strncpy(line_copy, line, MAX_LINE_LEN);
1765 line_copy[MAX_LINE_LEN] = '\0';
1768 strcpy(line_raw_copy, line_copy);
1769 line_raw = line_raw_copy;
1772 /* cut trailing comment from input line */
1773 for (line_ptr = line; *line_ptr; line_ptr++)
1775 if (*line_ptr == '#')
1782 /* cut trailing whitespaces from input line */
1783 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1784 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1787 /* ignore empty lines */
1791 /* cut leading whitespaces from token */
1792 for (token = line; *token; token++)
1793 if (*token != ' ' && *token != '\t')
1796 /* start with empty value as reliable default */
1799 token_value_separator_found = FALSE;
1801 /* find end of token to determine start of value */
1802 for (line_ptr = token; *line_ptr; line_ptr++)
1805 /* first look for an explicit token/value separator, like ':' or '=' */
1806 if (*line_ptr == ':' || *line_ptr == '=')
1808 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1811 *line_ptr = '\0'; /* terminate token string */
1812 value = line_ptr + 1; /* set beginning of value */
1814 token_value_separator_found = TRUE;
1820 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1821 /* fallback: if no token/value separator found, also allow whitespaces */
1822 if (!token_value_separator_found && !separator_required)
1824 for (line_ptr = token; *line_ptr; line_ptr++)
1826 if (*line_ptr == ' ' || *line_ptr == '\t')
1828 *line_ptr = '\0'; /* terminate token string */
1829 value = line_ptr + 1; /* set beginning of value */
1831 token_value_separator_found = TRUE;
1837 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1838 if (token_value_separator_found)
1840 if (!token_value_separator_warning)
1842 Error(ERR_INFO_LINE, "-");
1844 if (filename != NULL)
1846 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1847 Error(ERR_INFO, "- config file: '%s'", filename);
1851 Error(ERR_WARN, "missing token/value separator(s):");
1854 token_value_separator_warning = TRUE;
1857 if (filename != NULL)
1858 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1860 Error(ERR_INFO, "- line: '%s'", line_raw);
1866 /* cut trailing whitespaces from token */
1867 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1868 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1871 /* cut leading whitespaces from value */
1872 for (; *value; value++)
1873 if (*value != ' ' && *value != '\t')
1878 value = "true"; /* treat tokens without value as "true" */
1887 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1889 /* while the internal (old) interface does not require a token/value
1890 separator (for downwards compatibility with existing files which
1891 don't use them), it is mandatory for the external (new) interface */
1893 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1899 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1900 boolean top_recursion_level, boolean is_hash)
1902 static SetupFileHash *include_filename_hash = NULL;
1903 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1904 char *token, *value, *line_ptr;
1905 void *insert_ptr = NULL;
1906 boolean read_continued_line = FALSE;
1908 int line_nr = 0, token_count = 0, include_count = 0;
1910 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1911 token_value_separator_warning = FALSE;
1914 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1915 token_already_exists_warning = FALSE;
1919 Error(ERR_INFO, "===== opening file: '%s'", filename);
1922 if (!(file = openFile(filename, MODE_READ)))
1924 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1930 Error(ERR_INFO, "===== reading file: '%s'", filename);
1933 /* use "insert pointer" to store list end for constant insertion complexity */
1935 insert_ptr = setup_file_data;
1937 /* on top invocation, create hash to mark included files (to prevent loops) */
1938 if (top_recursion_level)
1939 include_filename_hash = newSetupFileHash();
1941 /* mark this file as already included (to prevent including it again) */
1942 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1944 while (!checkEndOfFile(file))
1946 /* read next line of input file */
1947 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1951 Error(ERR_INFO, "got line: '%s'", line);
1954 /* check if line was completely read and is terminated by line break */
1955 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1958 /* cut trailing line break (this can be newline and/or carriage return) */
1959 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1960 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1963 /* copy raw input line for later use (mainly debugging output) */
1964 strcpy(line_raw, line);
1966 if (read_continued_line)
1969 /* !!! ??? WHY ??? !!! */
1970 /* cut leading whitespaces from input line */
1971 for (line_ptr = line; *line_ptr; line_ptr++)
1972 if (*line_ptr != ' ' && *line_ptr != '\t')
1976 /* append new line to existing line, if there is enough space */
1977 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1978 strcat(previous_line, line_ptr);
1980 strcpy(line, previous_line); /* copy storage buffer to line */
1982 read_continued_line = FALSE;
1985 /* if the last character is '\', continue at next line */
1986 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1988 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1989 strcpy(previous_line, line); /* copy line to storage buffer */
1991 read_continued_line = TRUE;
1996 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1997 line_raw, line_nr, FALSE))
2002 if (strEqual(token, "include"))
2004 if (getHashEntry(include_filename_hash, value) == NULL)
2006 char *basepath = getBasePath(filename);
2007 char *basename = getBaseName(value);
2008 char *filename_include = getPath2(basepath, basename);
2011 Error(ERR_INFO, "[including file '%s']", filename_include);
2014 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2018 free(filename_include);
2024 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2031 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2033 getHashEntry((SetupFileHash *)setup_file_data, token);
2035 if (old_value != NULL)
2037 if (!token_already_exists_warning)
2039 Error(ERR_INFO_LINE, "-");
2040 Error(ERR_WARN, "duplicate token(s) found in config file:");
2041 Error(ERR_INFO, "- config file: '%s'", filename);
2043 token_already_exists_warning = TRUE;
2046 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2047 Error(ERR_INFO, " old value: '%s'", old_value);
2048 Error(ERR_INFO, " new value: '%s'", value);
2052 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2056 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2066 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2067 if (token_value_separator_warning)
2068 Error(ERR_INFO_LINE, "-");
2071 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2072 if (token_already_exists_warning)
2073 Error(ERR_INFO_LINE, "-");
2076 if (token_count == 0 && include_count == 0)
2077 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2079 if (top_recursion_level)
2080 freeSetupFileHash(include_filename_hash);
2087 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2088 boolean top_recursion_level, boolean is_hash)
2090 static SetupFileHash *include_filename_hash = NULL;
2091 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2092 char *token, *value, *line_ptr;
2093 void *insert_ptr = NULL;
2094 boolean read_continued_line = FALSE;
2096 int line_nr = 0, token_count = 0, include_count = 0;
2098 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2099 token_value_separator_warning = FALSE;
2102 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2103 token_already_exists_warning = FALSE;
2106 if (!(file = fopen(filename, MODE_READ)))
2108 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2113 /* use "insert pointer" to store list end for constant insertion complexity */
2115 insert_ptr = setup_file_data;
2117 /* on top invocation, create hash to mark included files (to prevent loops) */
2118 if (top_recursion_level)
2119 include_filename_hash = newSetupFileHash();
2121 /* mark this file as already included (to prevent including it again) */
2122 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2126 /* read next line of input file */
2127 if (!fgets(line, MAX_LINE_LEN, file))
2130 /* check if line was completely read and is terminated by line break */
2131 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2134 /* cut trailing line break (this can be newline and/or carriage return) */
2135 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2136 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2139 /* copy raw input line for later use (mainly debugging output) */
2140 strcpy(line_raw, line);
2142 if (read_continued_line)
2145 /* !!! ??? WHY ??? !!! */
2146 /* cut leading whitespaces from input line */
2147 for (line_ptr = line; *line_ptr; line_ptr++)
2148 if (*line_ptr != ' ' && *line_ptr != '\t')
2152 /* append new line to existing line, if there is enough space */
2153 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2154 strcat(previous_line, line_ptr);
2156 strcpy(line, previous_line); /* copy storage buffer to line */
2158 read_continued_line = FALSE;
2161 /* if the last character is '\', continue at next line */
2162 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2164 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2165 strcpy(previous_line, line); /* copy line to storage buffer */
2167 read_continued_line = TRUE;
2172 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2173 line_raw, line_nr, FALSE))
2178 if (strEqual(token, "include"))
2180 if (getHashEntry(include_filename_hash, value) == NULL)
2182 char *basepath = getBasePath(filename);
2183 char *basename = getBaseName(value);
2184 char *filename_include = getPath2(basepath, basename);
2187 Error(ERR_INFO, "[including file '%s']", filename_include);
2190 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2194 free(filename_include);
2200 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2207 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2209 getHashEntry((SetupFileHash *)setup_file_data, token);
2211 if (old_value != NULL)
2213 if (!token_already_exists_warning)
2215 Error(ERR_INFO_LINE, "-");
2216 Error(ERR_WARN, "duplicate token(s) found in config file:");
2217 Error(ERR_INFO, "- config file: '%s'", filename);
2219 token_already_exists_warning = TRUE;
2222 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2223 Error(ERR_INFO, " old value: '%s'", old_value);
2224 Error(ERR_INFO, " new value: '%s'", value);
2228 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2232 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2242 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2243 if (token_value_separator_warning)
2244 Error(ERR_INFO_LINE, "-");
2247 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2248 if (token_already_exists_warning)
2249 Error(ERR_INFO_LINE, "-");
2252 if (token_count == 0 && include_count == 0)
2253 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2255 if (top_recursion_level)
2256 freeSetupFileHash(include_filename_hash);
2265 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2266 boolean top_recursion_level, boolean is_hash)
2268 static SetupFileHash *include_filename_hash = NULL;
2269 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2270 char *token, *value, *line_ptr;
2271 void *insert_ptr = NULL;
2272 boolean read_continued_line = FALSE;
2275 int token_count = 0;
2277 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2278 token_value_separator_warning = FALSE;
2281 if (!(file = fopen(filename, MODE_READ)))
2283 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2288 /* use "insert pointer" to store list end for constant insertion complexity */
2290 insert_ptr = setup_file_data;
2292 /* on top invocation, create hash to mark included files (to prevent loops) */
2293 if (top_recursion_level)
2294 include_filename_hash = newSetupFileHash();
2296 /* mark this file as already included (to prevent including it again) */
2297 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2301 /* read next line of input file */
2302 if (!fgets(line, MAX_LINE_LEN, file))
2305 /* check if line was completely read and is terminated by line break */
2306 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2309 /* cut trailing line break (this can be newline and/or carriage return) */
2310 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2311 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2314 /* copy raw input line for later use (mainly debugging output) */
2315 strcpy(line_raw, line);
2317 if (read_continued_line)
2319 /* cut leading whitespaces from input line */
2320 for (line_ptr = line; *line_ptr; line_ptr++)
2321 if (*line_ptr != ' ' && *line_ptr != '\t')
2324 /* append new line to existing line, if there is enough space */
2325 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2326 strcat(previous_line, line_ptr);
2328 strcpy(line, previous_line); /* copy storage buffer to line */
2330 read_continued_line = FALSE;
2333 /* if the last character is '\', continue at next line */
2334 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2336 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2337 strcpy(previous_line, line); /* copy line to storage buffer */
2339 read_continued_line = TRUE;
2344 /* cut trailing comment from input line */
2345 for (line_ptr = line; *line_ptr; line_ptr++)
2347 if (*line_ptr == '#')
2354 /* cut trailing whitespaces from input line */
2355 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2356 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2359 /* ignore empty lines */
2363 /* cut leading whitespaces from token */
2364 for (token = line; *token; token++)
2365 if (*token != ' ' && *token != '\t')
2368 /* start with empty value as reliable default */
2371 token_value_separator_found = FALSE;
2373 /* find end of token to determine start of value */
2374 for (line_ptr = token; *line_ptr; line_ptr++)
2377 /* first look for an explicit token/value separator, like ':' or '=' */
2378 if (*line_ptr == ':' || *line_ptr == '=')
2380 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2383 *line_ptr = '\0'; /* terminate token string */
2384 value = line_ptr + 1; /* set beginning of value */
2386 token_value_separator_found = TRUE;
2392 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2393 /* fallback: if no token/value separator found, also allow whitespaces */
2394 if (!token_value_separator_found)
2396 for (line_ptr = token; *line_ptr; line_ptr++)
2398 if (*line_ptr == ' ' || *line_ptr == '\t')
2400 *line_ptr = '\0'; /* terminate token string */
2401 value = line_ptr + 1; /* set beginning of value */
2403 token_value_separator_found = TRUE;
2409 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2410 if (token_value_separator_found)
2412 if (!token_value_separator_warning)
2414 Error(ERR_INFO_LINE, "-");
2415 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2416 Error(ERR_INFO, "- config file: '%s'", filename);
2418 token_value_separator_warning = TRUE;
2421 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2427 /* cut trailing whitespaces from token */
2428 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2429 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2432 /* cut leading whitespaces from value */
2433 for (; *value; value++)
2434 if (*value != ' ' && *value != '\t')
2439 value = "true"; /* treat tokens without value as "true" */
2444 if (strEqual(token, "include"))
2446 if (getHashEntry(include_filename_hash, value) == NULL)
2448 char *basepath = getBasePath(filename);
2449 char *basename = getBaseName(value);
2450 char *filename_include = getPath2(basepath, basename);
2453 Error(ERR_INFO, "[including file '%s']", filename_include);
2456 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2460 free(filename_include);
2464 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2470 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2472 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2481 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2482 if (token_value_separator_warning)
2483 Error(ERR_INFO_LINE, "-");
2486 if (token_count == 0)
2487 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2489 if (top_recursion_level)
2490 freeSetupFileHash(include_filename_hash);
2496 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2500 if (!(file = fopen(filename, MODE_WRITE)))
2502 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2507 BEGIN_HASH_ITERATION(hash, itr)
2509 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2510 HASH_ITERATION_VALUE(itr)));
2512 END_HASH_ITERATION(hash, itr)
2517 SetupFileList *loadSetupFileList(char *filename)
2519 SetupFileList *setup_file_list = newSetupFileList("", "");
2520 SetupFileList *first_valid_list_entry;
2522 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2524 freeSetupFileList(setup_file_list);
2529 first_valid_list_entry = setup_file_list->next;
2531 /* free empty list header */
2532 setup_file_list->next = NULL;
2533 freeSetupFileList(setup_file_list);
2535 return first_valid_list_entry;
2538 SetupFileHash *loadSetupFileHash(char *filename)
2540 SetupFileHash *setup_file_hash = newSetupFileHash();
2542 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2544 freeSetupFileHash(setup_file_hash);
2549 return setup_file_hash;
2552 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2553 char *filename, char *identifier)
2555 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2558 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2559 else if (!checkCookieString(value, identifier))
2560 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2564 /* ========================================================================= */
2565 /* setup file stuff */
2566 /* ========================================================================= */
2568 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2569 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2570 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2572 /* level directory info */
2573 #define LEVELINFO_TOKEN_IDENTIFIER 0
2574 #define LEVELINFO_TOKEN_NAME 1
2575 #define LEVELINFO_TOKEN_NAME_SORTING 2
2576 #define LEVELINFO_TOKEN_AUTHOR 3
2577 #define LEVELINFO_TOKEN_YEAR 4
2578 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2579 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2580 #define LEVELINFO_TOKEN_TESTED_BY 7
2581 #define LEVELINFO_TOKEN_LEVELS 8
2582 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2583 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2584 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2585 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2586 #define LEVELINFO_TOKEN_READONLY 13
2587 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2588 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2589 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2590 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2591 #define LEVELINFO_TOKEN_MUSIC_SET 18
2592 #define LEVELINFO_TOKEN_FILENAME 19
2593 #define LEVELINFO_TOKEN_FILETYPE 20
2594 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2595 #define LEVELINFO_TOKEN_HANDICAP 22
2596 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2598 #define NUM_LEVELINFO_TOKENS 24
2600 static LevelDirTree ldi;
2602 static struct TokenInfo levelinfo_tokens[] =
2604 /* level directory info */
2605 { TYPE_STRING, &ldi.identifier, "identifier" },
2606 { TYPE_STRING, &ldi.name, "name" },
2607 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2608 { TYPE_STRING, &ldi.author, "author" },
2609 { TYPE_STRING, &ldi.year, "year" },
2610 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2611 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2612 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2613 { TYPE_INTEGER, &ldi.levels, "levels" },
2614 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2615 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2616 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2617 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2618 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2619 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2620 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2621 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2622 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2623 { TYPE_STRING, &ldi.music_set, "music_set" },
2624 { TYPE_STRING, &ldi.level_filename, "filename" },
2625 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2626 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2627 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2628 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2631 static struct TokenInfo artworkinfo_tokens[] =
2633 /* artwork directory info */
2634 { TYPE_STRING, &ldi.identifier, "identifier" },
2635 { TYPE_STRING, &ldi.subdir, "subdir" },
2636 { TYPE_STRING, &ldi.name, "name" },
2637 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2638 { TYPE_STRING, &ldi.author, "author" },
2639 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2640 { TYPE_STRING, &ldi.basepath, "basepath" },
2641 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2642 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2643 { TYPE_INTEGER, &ldi.color, "color" },
2644 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2649 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2653 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2654 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2655 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2656 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2659 ti->node_parent = NULL;
2660 ti->node_group = NULL;
2667 ti->fullpath = NULL;
2668 ti->basepath = NULL;
2669 ti->identifier = NULL;
2670 ti->name = getStringCopy(ANONYMOUS_NAME);
2671 ti->name_sorting = NULL;
2672 ti->author = getStringCopy(ANONYMOUS_NAME);
2675 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2676 ti->latest_engine = FALSE; /* default: get from level */
2677 ti->parent_link = FALSE;
2678 ti->in_user_dir = FALSE;
2679 ti->user_defined = FALSE;
2681 ti->class_desc = NULL;
2683 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2685 if (ti->type == TREE_TYPE_LEVEL_DIR)
2687 ti->imported_from = NULL;
2688 ti->imported_by = NULL;
2689 ti->tested_by = NULL;
2691 ti->graphics_set_ecs = NULL;
2692 ti->graphics_set_aga = NULL;
2693 ti->graphics_set = NULL;
2694 ti->sounds_set = NULL;
2695 ti->music_set = NULL;
2696 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2697 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2698 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2700 ti->level_filename = NULL;
2701 ti->level_filetype = NULL;
2703 ti->special_flags = NULL;
2706 ti->first_level = 0;
2708 ti->level_group = FALSE;
2709 ti->handicap_level = 0;
2710 ti->readonly = TRUE;
2711 ti->handicap = TRUE;
2712 ti->skip_levels = FALSE;
2716 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2720 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2722 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2727 /* copy all values from the parent structure */
2729 ti->type = parent->type;
2731 ti->node_top = parent->node_top;
2732 ti->node_parent = parent;
2733 ti->node_group = NULL;
2740 ti->fullpath = NULL;
2741 ti->basepath = NULL;
2742 ti->identifier = NULL;
2743 ti->name = getStringCopy(ANONYMOUS_NAME);
2744 ti->name_sorting = NULL;
2745 ti->author = getStringCopy(parent->author);
2746 ti->year = getStringCopy(parent->year);
2748 ti->sort_priority = parent->sort_priority;
2749 ti->latest_engine = parent->latest_engine;
2750 ti->parent_link = FALSE;
2751 ti->in_user_dir = parent->in_user_dir;
2752 ti->user_defined = parent->user_defined;
2753 ti->color = parent->color;
2754 ti->class_desc = getStringCopy(parent->class_desc);
2756 ti->infotext = getStringCopy(parent->infotext);
2758 if (ti->type == TREE_TYPE_LEVEL_DIR)
2760 ti->imported_from = getStringCopy(parent->imported_from);
2761 ti->imported_by = getStringCopy(parent->imported_by);
2762 ti->tested_by = getStringCopy(parent->tested_by);
2764 ti->graphics_set_ecs = NULL;
2765 ti->graphics_set_aga = NULL;
2766 ti->graphics_set = NULL;
2767 ti->sounds_set = NULL;
2768 ti->music_set = NULL;
2769 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2770 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2771 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2773 ti->level_filename = NULL;
2774 ti->level_filetype = NULL;
2776 ti->special_flags = getStringCopy(parent->special_flags);
2779 ti->first_level = 0;
2781 ti->level_group = FALSE;
2782 ti->handicap_level = 0;
2784 ti->readonly = parent->readonly;
2786 ti->readonly = TRUE;
2788 ti->handicap = TRUE;
2789 ti->skip_levels = FALSE;
2793 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2795 TreeInfo *ti_copy = newTreeInfo();
2797 /* copy all values from the original structure */
2799 ti_copy->type = ti->type;
2801 ti_copy->node_top = ti->node_top;
2802 ti_copy->node_parent = ti->node_parent;
2803 ti_copy->node_group = ti->node_group;
2804 ti_copy->next = ti->next;
2806 ti_copy->cl_first = ti->cl_first;
2807 ti_copy->cl_cursor = ti->cl_cursor;
2809 ti_copy->subdir = getStringCopy(ti->subdir);
2810 ti_copy->fullpath = getStringCopy(ti->fullpath);
2811 ti_copy->basepath = getStringCopy(ti->basepath);
2812 ti_copy->identifier = getStringCopy(ti->identifier);
2813 ti_copy->name = getStringCopy(ti->name);
2814 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2815 ti_copy->author = getStringCopy(ti->author);
2816 ti_copy->year = getStringCopy(ti->year);
2817 ti_copy->imported_from = getStringCopy(ti->imported_from);
2818 ti_copy->imported_by = getStringCopy(ti->imported_by);
2819 ti_copy->tested_by = getStringCopy(ti->tested_by);
2821 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2822 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2823 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2824 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2825 ti_copy->music_set = getStringCopy(ti->music_set);
2826 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2827 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2828 ti_copy->music_path = getStringCopy(ti->music_path);
2830 ti_copy->level_filename = getStringCopy(ti->level_filename);
2831 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2833 ti_copy->special_flags = getStringCopy(ti->special_flags);
2835 ti_copy->levels = ti->levels;
2836 ti_copy->first_level = ti->first_level;
2837 ti_copy->last_level = ti->last_level;
2838 ti_copy->sort_priority = ti->sort_priority;
2840 ti_copy->latest_engine = ti->latest_engine;
2842 ti_copy->level_group = ti->level_group;
2843 ti_copy->parent_link = ti->parent_link;
2844 ti_copy->in_user_dir = ti->in_user_dir;
2845 ti_copy->user_defined = ti->user_defined;
2846 ti_copy->readonly = ti->readonly;
2847 ti_copy->handicap = ti->handicap;
2848 ti_copy->skip_levels = ti->skip_levels;
2850 ti_copy->color = ti->color;
2851 ti_copy->class_desc = getStringCopy(ti->class_desc);
2852 ti_copy->handicap_level = ti->handicap_level;
2854 ti_copy->infotext = getStringCopy(ti->infotext);
2859 void freeTreeInfo(TreeInfo *ti)
2864 checked_free(ti->subdir);
2865 checked_free(ti->fullpath);
2866 checked_free(ti->basepath);
2867 checked_free(ti->identifier);
2869 checked_free(ti->name);
2870 checked_free(ti->name_sorting);
2871 checked_free(ti->author);
2872 checked_free(ti->year);
2874 checked_free(ti->class_desc);
2876 checked_free(ti->infotext);
2878 if (ti->type == TREE_TYPE_LEVEL_DIR)
2880 checked_free(ti->imported_from);
2881 checked_free(ti->imported_by);
2882 checked_free(ti->tested_by);
2884 checked_free(ti->graphics_set_ecs);
2885 checked_free(ti->graphics_set_aga);
2886 checked_free(ti->graphics_set);
2887 checked_free(ti->sounds_set);
2888 checked_free(ti->music_set);
2890 checked_free(ti->graphics_path);
2891 checked_free(ti->sounds_path);
2892 checked_free(ti->music_path);
2894 checked_free(ti->level_filename);
2895 checked_free(ti->level_filetype);
2897 checked_free(ti->special_flags);
2900 // recursively free child node
2902 freeTreeInfo(ti->node_group);
2904 // recursively free next node
2906 freeTreeInfo(ti->next);
2911 void setSetupInfo(struct TokenInfo *token_info,
2912 int token_nr, char *token_value)
2914 int token_type = token_info[token_nr].type;
2915 void *setup_value = token_info[token_nr].value;
2917 if (token_value == NULL)
2920 /* set setup field to corresponding token value */
2925 *(boolean *)setup_value = get_boolean_from_string(token_value);
2929 *(int *)setup_value = get_switch3_from_string(token_value);
2933 *(Key *)setup_value = getKeyFromKeyName(token_value);
2937 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2941 *(int *)setup_value = get_integer_from_string(token_value);
2945 checked_free(*(char **)setup_value);
2946 *(char **)setup_value = getStringCopy(token_value);
2954 static int compareTreeInfoEntries(const void *object1, const void *object2)
2956 const TreeInfo *entry1 = *((TreeInfo **)object1);
2957 const TreeInfo *entry2 = *((TreeInfo **)object2);
2958 int class_sorting1 = 0, class_sorting2 = 0;
2961 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2963 class_sorting1 = LEVELSORTING(entry1);
2964 class_sorting2 = LEVELSORTING(entry2);
2966 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2967 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2968 entry1->type == TREE_TYPE_MUSIC_DIR)
2970 class_sorting1 = ARTWORKSORTING(entry1);
2971 class_sorting2 = ARTWORKSORTING(entry2);
2974 if (entry1->parent_link || entry2->parent_link)
2975 compare_result = (entry1->parent_link ? -1 : +1);
2976 else if (entry1->sort_priority == entry2->sort_priority)
2978 char *name1 = getStringToLower(entry1->name_sorting);
2979 char *name2 = getStringToLower(entry2->name_sorting);
2981 compare_result = strcmp(name1, name2);
2986 else if (class_sorting1 == class_sorting2)
2987 compare_result = entry1->sort_priority - entry2->sort_priority;
2989 compare_result = class_sorting1 - class_sorting2;
2991 return compare_result;
2994 static void createParentTreeInfoNode(TreeInfo *node_parent)
2998 if (node_parent == NULL)
3001 ti_new = newTreeInfo();
3002 setTreeInfoToDefaults(ti_new, node_parent->type);
3004 ti_new->node_parent = node_parent;
3005 ti_new->parent_link = TRUE;
3007 setString(&ti_new->identifier, node_parent->identifier);
3008 setString(&ti_new->name, ".. (parent directory)");
3009 setString(&ti_new->name_sorting, ti_new->name);
3011 setString(&ti_new->subdir, "..");
3012 setString(&ti_new->fullpath, node_parent->fullpath);
3014 ti_new->sort_priority = node_parent->sort_priority;
3015 ti_new->latest_engine = node_parent->latest_engine;
3017 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3019 pushTreeInfo(&node_parent->node_group, ti_new);
3023 /* -------------------------------------------------------------------------- */
3024 /* functions for handling level and custom artwork info cache */
3025 /* -------------------------------------------------------------------------- */
3027 static void LoadArtworkInfoCache()
3029 InitCacheDirectory();
3031 if (artworkinfo_cache_old == NULL)
3033 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3035 /* try to load artwork info hash from already existing cache file */
3036 artworkinfo_cache_old = loadSetupFileHash(filename);
3038 /* if no artwork info cache file was found, start with empty hash */
3039 if (artworkinfo_cache_old == NULL)
3040 artworkinfo_cache_old = newSetupFileHash();
3045 if (artworkinfo_cache_new == NULL)
3046 artworkinfo_cache_new = newSetupFileHash();
3049 static void SaveArtworkInfoCache()
3051 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3053 InitCacheDirectory();
3055 saveSetupFileHash(artworkinfo_cache_new, filename);
3060 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3062 static char *prefix = NULL;
3064 checked_free(prefix);
3066 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3071 /* (identical to above function, but separate string buffer needed -- nasty) */
3072 static char *getCacheToken(char *prefix, char *suffix)
3074 static char *token = NULL;
3076 checked_free(token);
3078 token = getStringCat2WithSeparator(prefix, suffix, ".");
3083 static char *getFileTimestampString(char *filename)
3086 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3088 struct stat file_status;
3090 if (stat(filename, &file_status) != 0) /* cannot stat file */
3091 return getStringCopy(i_to_a(0));
3093 return getStringCopy(i_to_a(file_status.st_mtime));
3097 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3099 struct stat file_status;
3101 if (timestamp_string == NULL)
3104 if (stat(filename, &file_status) != 0) /* cannot stat file */
3107 return (file_status.st_mtime != atoi(timestamp_string));
3110 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3112 char *identifier = level_node->subdir;
3113 char *type_string = ARTWORK_DIRECTORY(type);
3114 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3115 char *token_main = getCacheToken(token_prefix, "CACHED");
3116 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3117 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3118 TreeInfo *artwork_info = NULL;
3120 if (!use_artworkinfo_cache)
3127 artwork_info = newTreeInfo();
3128 setTreeInfoToDefaults(artwork_info, type);
3130 /* set all structure fields according to the token/value pairs */
3131 ldi = *artwork_info;
3132 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3134 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3135 char *value = getHashEntry(artworkinfo_cache_old, token);
3137 setSetupInfo(artworkinfo_tokens, i, value);
3139 /* check if cache entry for this item is invalid or incomplete */
3143 Error(ERR_WARN, "cache entry '%s' invalid", token);
3150 *artwork_info = ldi;
3155 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3156 LEVELINFO_FILENAME);
3157 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3158 ARTWORKINFO_FILENAME(type));
3160 /* check if corresponding "levelinfo.conf" file has changed */
3161 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3162 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3164 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3167 /* check if corresponding "<artworkinfo>.conf" file has changed */
3168 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3169 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3171 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3176 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
3179 checked_free(filename_levelinfo);
3180 checked_free(filename_artworkinfo);
3183 if (!cached && artwork_info != NULL)
3185 freeTreeInfo(artwork_info);
3190 return artwork_info;
3193 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3194 LevelDirTree *level_node, int type)
3196 char *identifier = level_node->subdir;
3197 char *type_string = ARTWORK_DIRECTORY(type);
3198 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3199 char *token_main = getCacheToken(token_prefix, "CACHED");
3200 boolean set_cache_timestamps = TRUE;
3203 setHashEntry(artworkinfo_cache_new, token_main, "true");
3205 if (set_cache_timestamps)
3207 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3208 LEVELINFO_FILENAME);
3209 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3210 ARTWORKINFO_FILENAME(type));
3211 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3212 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3214 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3215 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3217 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3218 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3220 checked_free(filename_levelinfo);
3221 checked_free(filename_artworkinfo);
3222 checked_free(timestamp_levelinfo);
3223 checked_free(timestamp_artworkinfo);
3226 ldi = *artwork_info;
3227 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3229 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3230 char *value = getSetupValue(artworkinfo_tokens[i].type,
3231 artworkinfo_tokens[i].value);
3233 setHashEntry(artworkinfo_cache_new, token, value);
3238 /* -------------------------------------------------------------------------- */
3239 /* functions for loading level info and custom artwork info */
3240 /* -------------------------------------------------------------------------- */
3242 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3243 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3245 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3246 TreeInfo *node_parent,
3247 char *level_directory,
3248 char *directory_name)
3251 static unsigned int progress_delay = 0;
3252 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3254 char *directory_path = getPath2(level_directory, directory_name);
3255 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3256 SetupFileHash *setup_file_hash;
3257 LevelDirTree *leveldir_new = NULL;
3260 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3261 if (!options.debug && !fileExists(filename))
3263 free(directory_path);
3269 setup_file_hash = loadSetupFileHash(filename);
3271 if (setup_file_hash == NULL)
3273 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3275 free(directory_path);
3281 leveldir_new = newTreeInfo();
3284 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3286 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3288 leveldir_new->subdir = getStringCopy(directory_name);
3290 checkSetupFileHashIdentifier(setup_file_hash, filename,
3291 getCookie("LEVELINFO"));
3293 /* set all structure fields according to the token/value pairs */
3294 ldi = *leveldir_new;
3295 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3296 setSetupInfo(levelinfo_tokens, i,
3297 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3298 *leveldir_new = ldi;
3300 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3301 setString(&leveldir_new->name, leveldir_new->subdir);
3303 if (leveldir_new->identifier == NULL)
3304 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3306 if (leveldir_new->name_sorting == NULL)
3307 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3309 if (node_parent == NULL) /* top level group */
3311 leveldir_new->basepath = getStringCopy(level_directory);
3312 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3314 else /* sub level group */
3316 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3317 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3321 if (leveldir_new->levels < 1)
3322 leveldir_new->levels = 1;
3325 leveldir_new->last_level =
3326 leveldir_new->first_level + leveldir_new->levels - 1;
3328 leveldir_new->in_user_dir =
3329 (!strEqual(leveldir_new->basepath, options.level_directory));
3332 printf("::: '%s' -> %d\n",
3333 leveldir_new->identifier,
3334 leveldir_new->in_user_dir);
3337 /* adjust some settings if user's private level directory was detected */
3338 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3339 leveldir_new->in_user_dir &&
3340 (strEqual(leveldir_new->subdir, getLoginName()) ||
3341 strEqual(leveldir_new->name, getLoginName()) ||
3342 strEqual(leveldir_new->author, getRealName())))
3344 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3345 leveldir_new->readonly = FALSE;
3348 leveldir_new->user_defined =
3349 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3351 leveldir_new->color = LEVELCOLOR(leveldir_new);
3353 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3355 leveldir_new->handicap_level = /* set handicap to default value */
3356 (leveldir_new->user_defined || !leveldir_new->handicap ?
3357 leveldir_new->last_level : leveldir_new->first_level);
3361 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3362 leveldir_new->level_group);
3364 if (leveldir_new->level_group ||
3365 DelayReached(&progress_delay, progress_delay_value))
3366 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3369 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3373 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3375 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3377 /* skip level sets without levels (which are probably artwork base sets) */
3379 freeSetupFileHash(setup_file_hash);
3380 free(directory_path);
3388 pushTreeInfo(node_first, leveldir_new);
3390 freeSetupFileHash(setup_file_hash);
3392 if (leveldir_new->level_group)
3394 /* create node to link back to current level directory */
3395 createParentTreeInfoNode(leveldir_new);
3397 /* recursively step into sub-directory and look for more level series */
3398 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3399 leveldir_new, directory_path);
3402 free(directory_path);
3409 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3410 TreeInfo *node_parent,
3411 char *level_directory)
3414 DirectoryEntry *dir_entry;
3415 boolean valid_entry_found = FALSE;
3418 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3421 if ((dir = openDirectory(level_directory)) == NULL)
3423 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3429 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3432 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3434 char *directory_name = dir_entry->basename;
3435 char *directory_path = getPath2(level_directory, directory_name);
3438 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3441 /* skip entries for current and parent directory */
3442 if (strEqual(directory_name, ".") ||
3443 strEqual(directory_name, ".."))
3445 free(directory_path);
3451 /* find out if directory entry is itself a directory */
3452 if (!dir_entry->is_directory) /* not a directory */
3454 free(directory_path);
3457 Error(ERR_INFO, "* entry '%s' is not a directory ...", directory_name);
3463 /* find out if directory entry is itself a directory */
3464 struct stat file_status;
3465 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3466 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3468 free(directory_path);
3474 free(directory_path);
3476 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3477 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3478 strEqual(directory_name, MUSIC_DIRECTORY))
3481 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3486 closeDirectory(dir);
3488 /* special case: top level directory may directly contain "levelinfo.conf" */
3489 if (node_parent == NULL && !valid_entry_found)
3491 /* check if this directory directly contains a file "levelinfo.conf" */
3492 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3493 level_directory, ".");
3496 if (!valid_entry_found)
3497 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3503 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3504 TreeInfo *node_parent,
3505 char *level_directory)
3508 struct dirent *dir_entry;
3509 boolean valid_entry_found = FALSE;
3512 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3515 if ((dir = opendir(level_directory)) == NULL)
3517 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3523 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3526 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3528 struct stat file_status;
3529 char *directory_name = dir_entry->d_name;
3530 char *directory_path = getPath2(level_directory, directory_name);
3533 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3536 /* skip entries for current and parent directory */
3537 if (strEqual(directory_name, ".") ||
3538 strEqual(directory_name, ".."))
3540 free(directory_path);
3544 /* find out if directory entry is itself a directory */
3545 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3546 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3548 free(directory_path);
3552 free(directory_path);
3554 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3555 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3556 strEqual(directory_name, MUSIC_DIRECTORY))
3559 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3566 /* special case: top level directory may directly contain "levelinfo.conf" */
3567 if (node_parent == NULL && !valid_entry_found)
3569 /* check if this directory directly contains a file "levelinfo.conf" */
3570 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3571 level_directory, ".");
3574 if (!valid_entry_found)
3575 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3580 boolean AdjustGraphicsForEMC()
3582 boolean settings_changed = FALSE;
3584 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3585 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3587 return settings_changed;
3590 void LoadLevelInfo()
3592 InitUserLevelDirectory(getLoginName());
3594 DrawInitText("Loading level series", 120, FC_GREEN);
3596 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3597 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3599 /* after loading all level set information, clone the level directory tree
3600 and remove all level sets without levels (these may still contain artwork
3601 to be offered in the setup menu as "custom artwork", and are therefore
3602 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3603 leveldir_first_all = leveldir_first;
3604 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3606 AdjustGraphicsForEMC();
3608 /* before sorting, the first entries will be from the user directory */
3609 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3611 if (leveldir_first == NULL)
3612 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3614 sortTreeInfo(&leveldir_first);
3617 dumpTreeInfo(leveldir_first, 0);
3623 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3624 TreeInfo *node_parent,
3625 char *base_directory,
3626 char *directory_name, int type)
3628 char *directory_path = getPath2(base_directory, directory_name);
3629 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3630 SetupFileHash *setup_file_hash = NULL;
3631 TreeInfo *artwork_new = NULL;
3634 if (fileExists(filename))
3635 setup_file_hash = loadSetupFileHash(filename);
3637 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3640 DirectoryEntry *dir_entry;
3641 boolean valid_file_found = FALSE;
3643 if ((dir = openDirectory(directory_path)) != NULL)
3645 while ((dir_entry = readDirectory(dir)) != NULL)
3647 char *entry_name = dir_entry->basename;
3649 if (FileIsArtworkType(entry_name, type))
3651 valid_file_found = TRUE;
3657 closeDirectory(dir);
3660 if (!valid_file_found)
3662 if (!strEqual(directory_name, "."))
3663 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3665 free(directory_path);
3672 artwork_new = newTreeInfo();
3675 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3677 setTreeInfoToDefaults(artwork_new, type);
3679 artwork_new->subdir = getStringCopy(directory_name);
3681 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3684 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3687 /* set all structure fields according to the token/value pairs */
3689 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3690 setSetupInfo(levelinfo_tokens, i,
3691 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3694 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3695 setString(&artwork_new->name, artwork_new->subdir);
3697 if (artwork_new->identifier == NULL)
3698 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3700 if (artwork_new->name_sorting == NULL)
3701 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3704 if (node_parent == NULL) /* top level group */
3706 artwork_new->basepath = getStringCopy(base_directory);
3707 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3709 else /* sub level group */
3711 artwork_new->basepath = getStringCopy(node_parent->basepath);
3712 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3715 artwork_new->in_user_dir =
3716 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3718 /* (may use ".sort_priority" from "setup_file_hash" above) */
3719 artwork_new->color = ARTWORKCOLOR(artwork_new);
3721 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3723 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3725 if (strEqual(artwork_new->subdir, "."))
3727 if (artwork_new->user_defined)
3729 setString(&artwork_new->identifier, "private");
3730 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3734 setString(&artwork_new->identifier, "classic");
3735 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3738 /* set to new values after changing ".sort_priority" */
3739 artwork_new->color = ARTWORKCOLOR(artwork_new);
3741 setString(&artwork_new->class_desc,
3742 getLevelClassDescription(artwork_new));
3746 setString(&artwork_new->identifier, artwork_new->subdir);
3749 setString(&artwork_new->name, artwork_new->identifier);
3750 setString(&artwork_new->name_sorting, artwork_new->name);
3754 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3757 pushTreeInfo(node_first, artwork_new);
3759 freeSetupFileHash(setup_file_hash);
3761 free(directory_path);
3769 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3770 TreeInfo *node_parent,
3771 char *base_directory,
3772 char *directory_name, int type)
3774 char *directory_path = getPath2(base_directory, directory_name);
3775 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3776 SetupFileHash *setup_file_hash = NULL;
3777 TreeInfo *artwork_new = NULL;
3780 if (fileExists(filename))
3781 setup_file_hash = loadSetupFileHash(filename);
3783 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3786 struct dirent *dir_entry;
3787 boolean valid_file_found = FALSE;
3789 if ((dir = opendir(directory_path)) != NULL)
3791 while ((dir_entry = readdir(dir)) != NULL)
3793 char *entry_name = dir_entry->d_name;
3795 if (FileIsArtworkType(entry_name, type))
3797 valid_file_found = TRUE;
3805 if (!valid_file_found)
3807 if (!strEqual(directory_name, "."))
3808 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3810 free(directory_path);
3817 artwork_new = newTreeInfo();
3820 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3822 setTreeInfoToDefaults(artwork_new, type);
3824 artwork_new->subdir = getStringCopy(directory_name);
3826 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3829 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3832 /* set all structure fields according to the token/value pairs */
3834 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3835 setSetupInfo(levelinfo_tokens, i,
3836 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3839 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3840 setString(&artwork_new->name, artwork_new->subdir);
3842 if (artwork_new->identifier == NULL)
3843 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3845 if (artwork_new->name_sorting == NULL)
3846 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3849 if (node_parent == NULL) /* top level group */
3851 artwork_new->basepath = getStringCopy(base_directory);
3852 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3854 else /* sub level group */
3856 artwork_new->basepath = getStringCopy(node_parent->basepath);
3857 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3860 artwork_new->in_user_dir =
3861 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3863 /* (may use ".sort_priority" from "setup_file_hash" above) */
3864 artwork_new->color = ARTWORKCOLOR(artwork_new);
3866 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3868 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3870 if (strEqual(artwork_new->subdir, "."))
3872 if (artwork_new->user_defined)
3874 setString(&artwork_new->identifier, "private");
3875 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3879 setString(&artwork_new->identifier, "classic");
3880 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3883 /* set to new values after changing ".sort_priority" */
3884 artwork_new->color = ARTWORKCOLOR(artwork_new);
3886 setString(&artwork_new->class_desc,
3887 getLevelClassDescription(artwork_new));
3891 setString(&artwork_new->identifier, artwork_new->subdir);
3894 setString(&artwork_new->name, artwork_new->identifier);
3895 setString(&artwork_new->name_sorting, artwork_new->name);
3899 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3902 pushTreeInfo(node_first, artwork_new);
3904 freeSetupFileHash(setup_file_hash);
3906 free(directory_path);
3916 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3917 TreeInfo *node_parent,
3918 char *base_directory, int type)
3921 DirectoryEntry *dir_entry;
3922 boolean valid_entry_found = FALSE;
3924 if ((dir = openDirectory(base_directory)) == NULL)
3926 /* display error if directory is main "options.graphics_directory" etc. */
3927 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3928 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3933 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3935 char *directory_name = dir_entry->basename;
3936 char *directory_path = getPath2(base_directory, directory_name);
3938 /* skip directory entries for current and parent directory */
3939 if (strEqual(directory_name, ".") ||
3940 strEqual(directory_name, ".."))
3942 free(directory_path);
3948 /* skip directory entries which are not a directory */
3949 if (!dir_entry->is_directory) /* not a directory */
3951 free(directory_path);
3956 /* skip directory entries which are not a directory or are not accessible */
3957 struct stat file_status;
3958 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3959 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3961 free(directory_path);
3967 free(directory_path);
3969 /* check if this directory contains artwork with or without config file */
3970 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3972 directory_name, type);
3975 closeDirectory(dir);
3977 /* check if this directory directly contains artwork itself */
3978 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3979 base_directory, ".",
3981 if (!valid_entry_found)
3982 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3988 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3989 TreeInfo *node_parent,
3990 char *base_directory, int type)
3993 struct dirent *dir_entry;
3994 boolean valid_entry_found = FALSE;
3996 if ((dir = opendir(base_directory)) == NULL)
3998 /* display error if directory is main "options.graphics_directory" etc. */
3999 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4000 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
4005 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
4007 struct stat file_status;
4008 char *directory_name = dir_entry->d_name;
4009 char *directory_path = getPath2(base_directory, directory_name);
4011 /* skip directory entries for current and parent directory */
4012 if (strEqual(directory_name, ".") ||
4013 strEqual(directory_name, ".."))
4015 free(directory_path);
4019 /* skip directory entries which are not a directory or are not accessible */
4020 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
4021 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
4023 free(directory_path);
4027 free(directory_path);
4029 /* check if this directory contains artwork with or without config file */
4030 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4032 directory_name, type);
4037 /* check if this directory directly contains artwork itself */
4038 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4039 base_directory, ".",
4041 if (!valid_entry_found)
4042 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4048 static TreeInfo *getDummyArtworkInfo(int type)
4050 /* this is only needed when there is completely no artwork available */
4051 TreeInfo *artwork_new = newTreeInfo();
4053 setTreeInfoToDefaults(artwork_new, type);
4055 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4056 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4057 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4059 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4060 setString(&artwork_new->name, UNDEFINED_FILENAME);
4061 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4066 void LoadArtworkInfo()
4068 LoadArtworkInfoCache();
4070 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
4072 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4073 options.graphics_directory,
4074 TREE_TYPE_GRAPHICS_DIR);
4075 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4076 getUserGraphicsDir(),
4077 TREE_TYPE_GRAPHICS_DIR);
4079 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4080 options.sounds_directory,
4081 TREE_TYPE_SOUNDS_DIR);
4082 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4084 TREE_TYPE_SOUNDS_DIR);
4086 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4087 options.music_directory,
4088 TREE_TYPE_MUSIC_DIR);
4089 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4091 TREE_TYPE_MUSIC_DIR);
4093 if (artwork.gfx_first == NULL)
4094 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4095 if (artwork.snd_first == NULL)
4096 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4097 if (artwork.mus_first == NULL)
4098 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4100 /* before sorting, the first entries will be from the user directory */
4101 artwork.gfx_current =
4102 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4103 if (artwork.gfx_current == NULL)
4104 artwork.gfx_current =
4105 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4106 if (artwork.gfx_current == NULL)
4107 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4109 artwork.snd_current =
4110 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4111 if (artwork.snd_current == NULL)
4112 artwork.snd_current =
4113 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4114 if (artwork.snd_current == NULL)
4115 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4117 artwork.mus_current =
4118 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4119 if (artwork.mus_current == NULL)
4120 artwork.mus_current =
4121 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4122 if (artwork.mus_current == NULL)
4123 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4125 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4126 artwork.snd_current_identifier = artwork.snd_current->identifier;
4127 artwork.mus_current_identifier = artwork.mus_current->identifier;
4130 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
4131 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
4132 printf("music set == %s\n\n", artwork.mus_current_identifier);
4135 sortTreeInfo(&artwork.gfx_first);
4136 sortTreeInfo(&artwork.snd_first);
4137 sortTreeInfo(&artwork.mus_first);
4140 dumpTreeInfo(artwork.gfx_first, 0);
4141 dumpTreeInfo(artwork.snd_first, 0);
4142 dumpTreeInfo(artwork.mus_first, 0);
4146 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
4147 LevelDirTree *level_node)
4150 static unsigned int progress_delay = 0;
4151 unsigned int progress_delay_value = 100; /* (in milliseconds) */
4153 int type = (*artwork_node)->type;
4155 /* recursively check all level directories for artwork sub-directories */
4159 /* check all tree entries for artwork, but skip parent link entries */
4160 if (!level_node->parent_link)
4162 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4163 boolean cached = (artwork_new != NULL);
4167 pushTreeInfo(artwork_node, artwork_new);
4171 TreeInfo *topnode_last = *artwork_node;
4172 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4173 ARTWORK_DIRECTORY(type));
4175 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4177 if (topnode_last != *artwork_node) /* check for newly added node */
4179 artwork_new = *artwork_node;
4181 setString(&artwork_new->identifier, level_node->subdir);
4182 setString(&artwork_new->name, level_node->name);
4183 setString(&artwork_new->name_sorting, level_node->name_sorting);
4185 artwork_new->sort_priority = level_node->sort_priority;
4186 artwork_new->color = LEVELCOLOR(artwork_new);
4192 /* insert artwork info (from old cache or filesystem) into new cache */
4193 if (artwork_new != NULL)
4194 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4198 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
4199 level_node->level_group);
4201 if (level_node->level_group ||
4202 DelayReached(&progress_delay, progress_delay_value))
4203 DrawInitText(level_node->name, 150, FC_YELLOW);
4206 if (level_node->node_group != NULL)
4207 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
4209 level_node = level_node->next;
4213 void LoadLevelArtworkInfo()
4215 print_timestamp_init("LoadLevelArtworkInfo");
4217 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4219 print_timestamp_time("DrawTimeText");
4221 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
4222 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4223 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
4224 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4225 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
4226 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4228 SaveArtworkInfoCache();
4230 print_timestamp_time("SaveArtworkInfoCache");
4232 /* needed for reloading level artwork not known at ealier stage */
4234 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
4236 artwork.gfx_current =
4237 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4238 if (artwork.gfx_current == NULL)
4239 artwork.gfx_current =
4240 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4241 if (artwork.gfx_current == NULL)
4242 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4245 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
4247 artwork.snd_current =
4248 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4249 if (artwork.snd_current == NULL)
4250 artwork.snd_current =
4251 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4252 if (artwork.snd_current == NULL)
4253 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4256 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
4258 artwork.mus_current =
4259 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4260 if (artwork.mus_current == NULL)
4261 artwork.mus_current =
4262 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4263 if (artwork.mus_current == NULL)
4264 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4267 print_timestamp_time("getTreeInfoFromIdentifier");
4269 sortTreeInfo(&artwork.gfx_first);
4270 sortTreeInfo(&artwork.snd_first);
4271 sortTreeInfo(&artwork.mus_first);
4273 print_timestamp_time("sortTreeInfo");
4276 dumpTreeInfo(artwork.gfx_first, 0);
4277 dumpTreeInfo(artwork.snd_first, 0);
4278 dumpTreeInfo(artwork.mus_first, 0);
4281 print_timestamp_done("LoadLevelArtworkInfo");
4284 static void SaveUserLevelInfo()
4286 LevelDirTree *level_info;
4291 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
4293 if (!(file = fopen(filename, MODE_WRITE)))
4295 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4300 level_info = newTreeInfo();
4302 /* always start with reliable default values */
4303 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4305 setString(&level_info->name, getLoginName());
4306 setString(&level_info->author, getRealName());
4307 level_info->levels = 100;
4308 level_info->first_level = 1;
4310 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4312 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4313 getCookie("LEVELINFO")));
4316 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4318 if (i == LEVELINFO_TOKEN_NAME ||
4319 i == LEVELINFO_TOKEN_AUTHOR ||
4320 i == LEVELINFO_TOKEN_LEVELS ||
4321 i == LEVELINFO_TOKEN_FIRST_LEVEL)
4322 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4324 /* just to make things nicer :) */
4325 if (i == LEVELINFO_TOKEN_AUTHOR)
4326 fprintf(file, "\n");
4329 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4333 SetFilePermissions(filename, PERMS_PRIVATE);
4335 freeTreeInfo(level_info);
4339 char *getSetupValue(int type, void *value)
4341 static char value_string[MAX_LINE_LEN];
4349 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4353 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4357 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4358 *(int *)value == FALSE ? "off" : "on"));
4362 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4365 case TYPE_YES_NO_AUTO:
4366 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4367 *(int *)value == FALSE ? "no" : "yes"));
4371 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4375 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4379 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4383 sprintf(value_string, "%d", *(int *)value);
4387 if (*(char **)value == NULL)
4390 strcpy(value_string, *(char **)value);
4394 value_string[0] = '\0';
4398 if (type & TYPE_GHOSTED)
4399 strcpy(value_string, "n/a");
4401 return value_string;
4404 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4408 static char token_string[MAX_LINE_LEN];
4409 int token_type = token_info[token_nr].type;
4410 void *setup_value = token_info[token_nr].value;
4411 char *token_text = token_info[token_nr].text;
4412 char *value_string = getSetupValue(token_type, setup_value);
4414 /* build complete token string */
4415 sprintf(token_string, "%s%s", prefix, token_text);
4417 /* build setup entry line */
4418 line = getFormattedSetupEntry(token_string, value_string);
4420 if (token_type == TYPE_KEY_X11)
4422 Key key = *(Key *)setup_value;
4423 char *keyname = getKeyNameFromKey(key);
4425 /* add comment, if useful */
4426 if (!strEqual(keyname, "(undefined)") &&
4427 !strEqual(keyname, "(unknown)"))
4429 /* add at least one whitespace */
4431 for (i = strlen(line); i < token_comment_position; i++)
4435 strcat(line, keyname);
4442 void LoadLevelSetup_LastSeries()
4444 /* ----------------------------------------------------------------------- */
4445 /* ~/.<program>/levelsetup.conf */
4446 /* ----------------------------------------------------------------------- */
4448 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4449 SetupFileHash *level_setup_hash = NULL;
4451 /* always start with reliable default values */
4452 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4454 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
4455 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4457 if (leveldir_current == NULL)
4458 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4461 if ((level_setup_hash = loadSetupFileHash(filename)))
4463 char *last_level_series =
4464 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4466 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4468 if (leveldir_current == NULL)
4469 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4471 checkSetupFileHashIdentifier(level_setup_hash, filename,
4472 getCookie("LEVELSETUP"));
4474 freeSetupFileHash(level_setup_hash);
4477 Error(ERR_WARN, "using default setup values");
4482 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4484 /* ----------------------------------------------------------------------- */
4485 /* ~/.<program>/levelsetup.conf */
4486 /* ----------------------------------------------------------------------- */
4488 // check if the current level directory structure is available at this point
4489 if (leveldir_current == NULL)
4492 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4493 char *level_subdir = leveldir_current->subdir;
4496 InitUserDataDirectory();
4498 if (!(file = fopen(filename, MODE_WRITE)))
4500 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4507 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4508 getCookie("LEVELSETUP")));
4510 if (deactivate_last_level_series)
4511 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4513 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4518 SetFilePermissions(filename, PERMS_PRIVATE);
4523 void SaveLevelSetup_LastSeries()
4525 SaveLevelSetup_LastSeries_Ext(FALSE);
4528 void SaveLevelSetup_LastSeries_Deactivate()
4530 SaveLevelSetup_LastSeries_Ext(TRUE);
4535 static void checkSeriesInfo()
4537 static char *level_directory = NULL;
4540 DirectoryEntry *dir_entry;
4543 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4545 level_directory = getPath2((leveldir_current->in_user_dir ?
4546 getUserLevelDir(NULL) :
4547 options.level_directory),
4548 leveldir_current->fullpath);
4550 if ((dir = openDirectory(level_directory)) == NULL)
4552 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4558 while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */
4560 if (strlen(dir_entry->basename) > 4 &&
4561 dir_entry->basename[3] == '.' &&
4562 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4564 char levelnum_str[4];
4567 strncpy(levelnum_str, dir_entry->basename, 3);
4568 levelnum_str[3] = '\0';
4570 levelnum_value = atoi(levelnum_str);
4572 if (levelnum_value < leveldir_current->first_level)
4574 Error(ERR_WARN, "additional level %d found", levelnum_value);
4575 leveldir_current->first_level = levelnum_value;
4577 else if (levelnum_value > leveldir_current->last_level)
4579 Error(ERR_WARN, "additional level %d found", levelnum_value);
4580 leveldir_current->last_level = levelnum_value;
4586 closeDirectory(dir);
4591 static void checkSeriesInfo()
4593 static char *level_directory = NULL;
4596 struct dirent *dir_entry;
4599 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4601 level_directory = getPath2((leveldir_current->in_user_dir ?
4602 getUserLevelDir(NULL) :
4603 options.level_directory),
4604 leveldir_current->fullpath);
4606 if ((dir = opendir(level_directory)) == NULL)
4608 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4614 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4616 if (strlen(dir_entry->d_name) > 4 &&
4617 dir_entry->d_name[3] == '.' &&
4618 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4620 char levelnum_str[4];
4623 strncpy(levelnum_str, dir_entry->d_name, 3);
4624 levelnum_str[3] = '\0';
4626 levelnum_value = atoi(levelnum_str);
4628 if (levelnum_value < leveldir_current->first_level)
4630 Error(ERR_WARN, "additional level %d found", levelnum_value);
4631 leveldir_current->first_level = levelnum_value;
4633 else if (levelnum_value > leveldir_current->last_level)
4635 Error(ERR_WARN, "additional level %d found", levelnum_value);
4636 leveldir_current->last_level = levelnum_value;
4647 void LoadLevelSetup_SeriesInfo()
4650 SetupFileHash *level_setup_hash = NULL;
4651 char *level_subdir = leveldir_current->subdir;
4654 /* always start with reliable default values */
4655 level_nr = leveldir_current->first_level;
4657 for (i = 0; i < MAX_LEVELS; i++)
4659 LevelStats_setPlayed(i, 0);
4660 LevelStats_setSolved(i, 0);
4663 checkSeriesInfo(leveldir_current);
4665 /* ----------------------------------------------------------------------- */
4666 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4667 /* ----------------------------------------------------------------------- */
4669 level_subdir = leveldir_current->subdir;
4671 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4673 if ((level_setup_hash = loadSetupFileHash(filename)))
4677 /* get last played level in this level set */
4679 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4683 level_nr = atoi(token_value);
4685 if (level_nr < leveldir_current->first_level)
4686 level_nr = leveldir_current->first_level;
4687 if (level_nr > leveldir_current->last_level)
4688 level_nr = leveldir_current->last_level;
4691 /* get handicap level in this level set */
4693 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4697 int level_nr = atoi(token_value);
4699 if (level_nr < leveldir_current->first_level)
4700 level_nr = leveldir_current->first_level;
4701 if (level_nr > leveldir_current->last_level + 1)
4702 level_nr = leveldir_current->last_level;
4704 if (leveldir_current->user_defined || !leveldir_current->handicap)
4705 level_nr = leveldir_current->last_level;
4707 leveldir_current->handicap_level = level_nr;
4710 /* get number of played and solved levels in this level set */
4712 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4714 char *token = HASH_ITERATION_TOKEN(itr);
4715 char *value = HASH_ITERATION_VALUE(itr);
4717 if (strlen(token) == 3 &&
4718 token[0] >= '0' && token[0] <= '9' &&
4719 token[1] >= '0' && token[1] <= '9' &&
4720 token[2] >= '0' && token[2] <= '9')
4722 int level_nr = atoi(token);
4725 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4727 value = strchr(value, ' ');
4730 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4733 END_HASH_ITERATION(hash, itr)
4735 checkSetupFileHashIdentifier(level_setup_hash, filename,
4736 getCookie("LEVELSETUP"));
4738 freeSetupFileHash(level_setup_hash);
4741 Error(ERR_WARN, "using default setup values");
4746 void SaveLevelSetup_SeriesInfo()
4749 char *level_subdir = leveldir_current->subdir;
4750 char *level_nr_str = int2str(level_nr, 0);
4751 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4755 /* ----------------------------------------------------------------------- */
4756 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4757 /* ----------------------------------------------------------------------- */
4759 InitLevelSetupDirectory(level_subdir);
4761 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4763 if (!(file = fopen(filename, MODE_WRITE)))
4765 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4770 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4771 getCookie("LEVELSETUP")));
4772 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4774 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4775 handicap_level_str));
4777 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4780 if (LevelStats_getPlayed(i) > 0 ||
4781 LevelStats_getSolved(i) > 0)
4786 sprintf(token, "%03d", i);
4787 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4789 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4795 SetFilePermissions(filename, PERMS_PRIVATE);
4800 int LevelStats_getPlayed(int nr)
4802 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4805 int LevelStats_getSolved(int nr)
4807 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4810 void LevelStats_setPlayed(int nr, int value)
4812 if (nr >= 0 && nr < MAX_LEVELS)
4813 level_stats[nr].played = value;
4816 void LevelStats_setSolved(int nr, int value)
4818 if (nr >= 0 && nr < MAX_LEVELS)
4819 level_stats[nr].solved = value;
4822 void LevelStats_incPlayed(int nr)
4824 if (nr >= 0 && nr < MAX_LEVELS)
4825 level_stats[nr].played++;
4828 void LevelStats_incSolved(int nr)
4830 if (nr >= 0 && nr < MAX_LEVELS)
4831 level_stats[nr].solved++;