1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getDefaultArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getDefaultArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
341 normally result in using the artwork configured in the setup menu. But
342 if an artwork subdirectory exists (which might contain custom artwork
343 or an artwork configuration file), this level artwork must be treated
344 as relative to the default "classic" artwork, not to the artwork that
345 is currently configured in the setup menu. */
347 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
349 checked_free(*artwork_set_ptr);
353 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
354 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
358 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
359 *artwork_set_ptr = NULL;
365 return *artwork_set_ptr;
368 inline static char *getLevelArtworkSet(int type)
370 if (leveldir_current == NULL)
373 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
376 inline static char *getLevelArtworkDir(int type)
378 if (leveldir_current == NULL)
379 return UNDEFINED_FILENAME;
381 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
384 char *getTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
397 char *getSolutionTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getSolutionTapeDir(), basename);
410 char *getScoreFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
418 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
423 char *getSetupFilename()
425 static char *filename = NULL;
427 checked_free(filename);
429 filename = getPath2(getSetupDir(), SETUP_FILENAME);
434 char *getEditorSetupFilename()
436 static char *filename = NULL;
438 checked_free(filename);
439 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
441 if (fileExists(filename))
444 checked_free(filename);
445 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
450 char *getHelpAnimFilename()
452 static char *filename = NULL;
454 checked_free(filename);
456 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
461 char *getHelpTextFilename()
463 static char *filename = NULL;
465 checked_free(filename);
467 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
472 char *getLevelSetInfoFilename()
474 static char *filename = NULL;
489 for (i = 0; basenames[i] != NULL; i++)
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), basenames[i]);
494 if (fileExists(filename))
501 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
503 static char basename[32];
505 sprintf(basename, "%s_%d.txt",
506 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
511 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
513 static char *filename = NULL;
515 boolean skip_setup_artwork = FALSE;
517 checked_free(filename);
519 basename = getLevelSetTitleMessageBasename(nr, initial);
521 if (!setup.override_level_graphics)
523 /* 1st try: look for special artwork in current level series directory */
524 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
525 if (fileExists(filename))
530 /* 2nd try: look for message file in current level set directory */
531 filename = getPath2(getCurrentLevelDir(), basename);
532 if (fileExists(filename))
537 /* check if there is special artwork configured in level series config */
538 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
540 /* 3rd try: look for special artwork configured in level series config */
541 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
542 if (fileExists(filename))
547 /* take missing artwork configured in level set config from default */
548 skip_setup_artwork = TRUE;
552 if (!skip_setup_artwork)
554 /* 4th try: look for special artwork in configured artwork directory */
555 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
556 if (fileExists(filename))
562 /* 5th try: look for default artwork in new default artwork directory */
563 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
564 if (fileExists(filename))
569 /* 6th try: look for default artwork in old default artwork directory */
570 filename = getPath2(options.graphics_directory, basename);
571 if (fileExists(filename))
574 return NULL; /* cannot find specified artwork file anywhere */
577 static char *getCorrectedArtworkBasename(char *basename)
579 char *basename_corrected = basename;
581 #if defined(PLATFORM_MSDOS)
582 if (program.filename_prefix != NULL)
584 int prefix_len = strlen(program.filename_prefix);
586 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
587 basename_corrected = &basename[prefix_len];
589 /* if corrected filename is still longer than standard MS-DOS filename
590 size (8 characters + 1 dot + 3 characters file extension), shorten
591 filename by writing file extension after 8th basename character */
592 if (strlen(basename_corrected) > 8 + 1 + 3)
594 static char *msdos_filename = NULL;
596 checked_free(msdos_filename);
598 msdos_filename = getStringCopy(basename_corrected);
599 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
601 basename_corrected = msdos_filename;
606 return basename_corrected;
609 char *getCustomImageFilename(char *basename)
611 static char *filename = NULL;
612 boolean skip_setup_artwork = FALSE;
614 checked_free(filename);
616 basename = getCorrectedArtworkBasename(basename);
618 if (!setup.override_level_graphics)
620 /* 1st try: look for special artwork in current level series directory */
621 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
622 if (fileExists(filename))
627 /* check if there is special artwork configured in level series config */
628 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
630 /* 2nd try: look for special artwork configured in level series config */
631 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
632 if (fileExists(filename))
637 /* take missing artwork configured in level set config from default */
638 skip_setup_artwork = TRUE;
642 if (!skip_setup_artwork)
644 /* 3rd try: look for special artwork in configured artwork directory */
645 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
646 if (fileExists(filename))
652 /* 4th try: look for default artwork in new default artwork directory */
653 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
654 if (fileExists(filename))
659 /* 5th try: look for default artwork in old default artwork directory */
660 filename = getPath2(options.graphics_directory, basename);
661 if (fileExists(filename))
664 #if CREATE_SPECIAL_EDITION
667 /* 6th try: look for fallback artwork in old default artwork directory */
668 /* (needed to prevent errors when trying to access unused artwork files) */
669 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
670 if (fileExists(filename))
674 return NULL; /* cannot find specified artwork file anywhere */
677 char *getCustomSoundFilename(char *basename)
679 static char *filename = NULL;
680 boolean skip_setup_artwork = FALSE;
682 checked_free(filename);
684 basename = getCorrectedArtworkBasename(basename);
686 if (!setup.override_level_sounds)
688 /* 1st try: look for special artwork in current level series directory */
689 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
690 if (fileExists(filename))
695 /* check if there is special artwork configured in level series config */
696 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
698 /* 2nd try: look for special artwork configured in level series config */
699 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
700 if (fileExists(filename))
705 /* take missing artwork configured in level set config from default */
706 skip_setup_artwork = TRUE;
710 if (!skip_setup_artwork)
712 /* 3rd try: look for special artwork in configured artwork directory */
713 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
714 if (fileExists(filename))
720 /* 4th try: look for default artwork in new default artwork directory */
721 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
722 if (fileExists(filename))
727 /* 5th try: look for default artwork in old default artwork directory */
728 filename = getPath2(options.sounds_directory, basename);
729 if (fileExists(filename))
732 #if CREATE_SPECIAL_EDITION
735 /* 6th try: look for fallback artwork in old default artwork directory */
736 /* (needed to prevent errors when trying to access unused artwork files) */
737 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
738 if (fileExists(filename))
742 return NULL; /* cannot find specified artwork file anywhere */
745 char *getCustomMusicFilename(char *basename)
747 static char *filename = NULL;
748 boolean skip_setup_artwork = FALSE;
750 checked_free(filename);
752 basename = getCorrectedArtworkBasename(basename);
754 if (!setup.override_level_music)
756 /* 1st try: look for special artwork in current level series directory */
757 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
758 if (fileExists(filename))
763 /* check if there is special artwork configured in level series config */
764 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
766 /* 2nd try: look for special artwork configured in level series config */
767 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
768 if (fileExists(filename))
773 /* take missing artwork configured in level set config from default */
774 skip_setup_artwork = TRUE;
778 if (!skip_setup_artwork)
780 /* 3rd try: look for special artwork in configured artwork directory */
781 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
782 if (fileExists(filename))
788 /* 4th try: look for default artwork in new default artwork directory */
789 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
790 if (fileExists(filename))
795 /* 5th try: look for default artwork in old default artwork directory */
796 filename = getPath2(options.music_directory, basename);
797 if (fileExists(filename))
800 #if CREATE_SPECIAL_EDITION
803 /* 6th try: look for fallback artwork in old default artwork directory */
804 /* (needed to prevent errors when trying to access unused artwork files) */
805 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
806 if (fileExists(filename))
810 return NULL; /* cannot find specified artwork file anywhere */
813 char *getCustomArtworkFilename(char *basename, int type)
815 if (type == ARTWORK_TYPE_GRAPHICS)
816 return getCustomImageFilename(basename);
817 else if (type == ARTWORK_TYPE_SOUNDS)
818 return getCustomSoundFilename(basename);
819 else if (type == ARTWORK_TYPE_MUSIC)
820 return getCustomMusicFilename(basename);
822 return UNDEFINED_FILENAME;
825 char *getCustomArtworkConfigFilename(int type)
827 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
830 char *getCustomArtworkLevelConfigFilename(int type)
832 static char *filename = NULL;
834 checked_free(filename);
836 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
841 char *getCustomMusicDirectory(void)
843 static char *directory = NULL;
844 boolean skip_setup_artwork = FALSE;
846 checked_free(directory);
848 if (!setup.override_level_music)
850 /* 1st try: look for special artwork in current level series directory */
851 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
852 if (fileExists(directory))
857 /* check if there is special artwork configured in level series config */
858 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
860 /* 2nd try: look for special artwork configured in level series config */
861 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
862 if (fileExists(directory))
867 /* take missing artwork configured in level set config from default */
868 skip_setup_artwork = TRUE;
872 if (!skip_setup_artwork)
874 /* 3rd try: look for special artwork in configured artwork directory */
875 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
876 if (fileExists(directory))
882 /* 4th try: look for default artwork in new default artwork directory */
883 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
884 if (fileExists(directory))
889 /* 5th try: look for default artwork in old default artwork directory */
890 directory = getStringCopy(options.music_directory);
891 if (fileExists(directory))
894 return NULL; /* cannot find specified artwork file anywhere */
897 void InitTapeDirectory(char *level_subdir)
899 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
900 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
901 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
904 void InitScoreDirectory(char *level_subdir)
906 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
907 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
908 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
911 static void SaveUserLevelInfo();
913 void InitUserLevelDirectory(char *level_subdir)
915 if (!fileExists(getUserLevelDir(level_subdir)))
917 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
918 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
919 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
925 void InitLevelSetupDirectory(char *level_subdir)
927 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
928 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
929 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
932 void InitCacheDirectory()
934 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
935 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
939 /* ------------------------------------------------------------------------- */
940 /* some functions to handle lists of level and artwork directories */
941 /* ------------------------------------------------------------------------- */
943 TreeInfo *newTreeInfo()
945 return checked_calloc(sizeof(TreeInfo));
948 TreeInfo *newTreeInfo_setDefaults(int type)
950 TreeInfo *ti = newTreeInfo();
952 setTreeInfoToDefaults(ti, type);
957 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
959 node_new->next = *node_first;
960 *node_first = node_new;
963 int numTreeInfo(TreeInfo *node)
976 boolean validLevelSeries(TreeInfo *node)
978 return (node != NULL && !node->node_group && !node->parent_link);
981 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
986 if (node->node_group) /* enter level group (step down into tree) */
987 return getFirstValidTreeInfoEntry(node->node_group);
988 else if (node->parent_link) /* skip start entry of level group */
990 if (node->next) /* get first real level series entry */
991 return getFirstValidTreeInfoEntry(node->next);
992 else /* leave empty level group and go on */
993 return getFirstValidTreeInfoEntry(node->node_parent->next);
995 else /* this seems to be a regular level series */
999 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1004 if (node->node_parent == NULL) /* top level group */
1005 return *node->node_top;
1006 else /* sub level group */
1007 return node->node_parent->node_group;
1010 int numTreeInfoInGroup(TreeInfo *node)
1012 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1015 int posTreeInfo(TreeInfo *node)
1017 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1022 if (node_cmp == node)
1026 node_cmp = node_cmp->next;
1032 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1034 TreeInfo *node_default = node;
1046 return node_default;
1049 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1051 if (identifier == NULL)
1056 if (node->node_group)
1058 TreeInfo *node_group;
1060 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1065 else if (!node->parent_link)
1067 if (strEqual(identifier, node->identifier))
1077 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1078 TreeInfo *node, boolean skip_sets_without_levels)
1085 if (!node->parent_link && !node->level_group &&
1086 skip_sets_without_levels && node->levels == 0)
1087 return cloneTreeNode(node_top, node_parent, node->next,
1088 skip_sets_without_levels);
1091 node_new = getTreeInfoCopy(node); /* copy complete node */
1093 node_new = newTreeInfo();
1095 *node_new = *node; /* copy complete node */
1098 node_new->node_top = node_top; /* correct top node link */
1099 node_new->node_parent = node_parent; /* correct parent node link */
1101 if (node->level_group)
1102 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1103 skip_sets_without_levels);
1105 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1106 skip_sets_without_levels);
1111 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1113 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1115 *ti_new = ti_cloned;
1118 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1120 boolean settings_changed = FALSE;
1124 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1125 !strEqual(node->graphics_set, node->graphics_set_ecs))
1127 setString(&node->graphics_set, node->graphics_set_ecs);
1128 settings_changed = TRUE;
1130 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1131 !strEqual(node->graphics_set, node->graphics_set_aga))
1133 setString(&node->graphics_set, node->graphics_set_aga);
1134 settings_changed = TRUE;
1137 if (node->node_group != NULL)
1138 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1143 return settings_changed;
1146 void dumpTreeInfo(TreeInfo *node, int depth)
1150 printf("Dumping TreeInfo:\n");
1154 for (i = 0; i < (depth + 1) * 3; i++)
1157 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1158 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1160 if (node->node_group != NULL)
1161 dumpTreeInfo(node->node_group, depth + 1);
1167 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1168 int (*compare_function)(const void *,
1171 int num_nodes = numTreeInfo(*node_first);
1172 TreeInfo **sort_array;
1173 TreeInfo *node = *node_first;
1179 /* allocate array for sorting structure pointers */
1180 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1182 /* writing structure pointers to sorting array */
1183 while (i < num_nodes && node) /* double boundary check... */
1185 sort_array[i] = node;
1191 /* sorting the structure pointers in the sorting array */
1192 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1195 /* update the linkage of list elements with the sorted node array */
1196 for (i = 0; i < num_nodes - 1; i++)
1197 sort_array[i]->next = sort_array[i + 1];
1198 sort_array[num_nodes - 1]->next = NULL;
1200 /* update the linkage of the main list anchor pointer */
1201 *node_first = sort_array[0];
1205 /* now recursively sort the level group structures */
1209 if (node->node_group != NULL)
1210 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1216 void sortTreeInfo(TreeInfo **node_first)
1218 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1222 /* ========================================================================= */
1223 /* some stuff from "files.c" */
1224 /* ========================================================================= */
1226 #if defined(PLATFORM_WIN32)
1228 #define S_IRGRP S_IRUSR
1231 #define S_IROTH S_IRUSR
1234 #define S_IWGRP S_IWUSR
1237 #define S_IWOTH S_IWUSR
1240 #define S_IXGRP S_IXUSR
1243 #define S_IXOTH S_IXUSR
1246 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1251 #endif /* PLATFORM_WIN32 */
1253 /* file permissions for newly written files */
1254 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1255 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1256 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1258 #define MODE_W_PRIVATE (S_IWUSR)
1259 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1260 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1262 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1263 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1265 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1266 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1270 static char *dir = NULL;
1272 #if defined(PLATFORM_WIN32)
1275 dir = checked_malloc(MAX_PATH + 1);
1277 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1280 #elif defined(PLATFORM_UNIX)
1283 if ((dir = getenv("HOME")) == NULL)
1287 if ((pwd = getpwuid(getuid())) != NULL)
1288 dir = getStringCopy(pwd->pw_dir);
1300 char *getCommonDataDir(void)
1302 static char *common_data_dir = NULL;
1304 #if defined(PLATFORM_WIN32)
1305 if (common_data_dir == NULL)
1307 char *dir = checked_malloc(MAX_PATH + 1);
1309 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1310 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1311 common_data_dir = getPath2(dir, program.userdata_subdir);
1313 common_data_dir = options.rw_base_directory;
1316 if (common_data_dir == NULL)
1317 common_data_dir = options.rw_base_directory;
1320 return common_data_dir;
1323 char *getPersonalDataDir(void)
1325 static char *personal_data_dir = NULL;
1327 #if defined(PLATFORM_MACOSX)
1328 if (personal_data_dir == NULL)
1329 personal_data_dir = getPath2(getHomeDir(), "Documents");
1331 if (personal_data_dir == NULL)
1332 personal_data_dir = getHomeDir();
1335 return personal_data_dir;
1338 char *getUserGameDataDir(void)
1340 static char *user_game_data_dir = NULL;
1342 if (user_game_data_dir == NULL)
1343 user_game_data_dir = getPath2(getPersonalDataDir(),
1344 program.userdata_subdir);
1346 return user_game_data_dir;
1349 void updateUserGameDataDir()
1351 #if defined(PLATFORM_MACOSX)
1352 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1353 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1355 /* convert old Unix style game data directory to Mac OS X style, if needed */
1356 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1358 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1360 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1361 userdata_dir_old, userdata_dir_new);
1363 /* continue using Unix style data directory -- this should not happen */
1364 program.userdata_path = getPath2(getPersonalDataDir(),
1365 program.userdata_subdir_unix);
1369 free(userdata_dir_old);
1375 return getUserGameDataDir();
1378 static mode_t posix_umask(mode_t mask)
1380 #if defined(PLATFORM_UNIX)
1387 static int posix_mkdir(const char *pathname, mode_t mode)
1389 #if defined(PLATFORM_WIN32)
1390 return mkdir(pathname);
1392 return mkdir(pathname, mode);
1396 void createDirectory(char *dir, char *text, int permission_class)
1398 /* leave "other" permissions in umask untouched, but ensure group parts
1399 of USERDATA_DIR_MODE are not masked */
1400 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1401 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1402 mode_t normal_umask = posix_umask(0);
1403 mode_t group_umask = ~(dir_mode & S_IRWXG);
1404 posix_umask(normal_umask & group_umask);
1406 if (!fileExists(dir))
1407 if (posix_mkdir(dir, dir_mode) != 0)
1408 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1410 posix_umask(normal_umask); /* reset normal umask */
1413 void InitUserDataDirectory()
1415 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1418 void SetFilePermissions(char *filename, int permission_class)
1420 chmod(filename, (permission_class == PERMS_PRIVATE ?
1421 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1424 char *getCookie(char *file_type)
1426 static char cookie[MAX_COOKIE_LEN + 1];
1428 if (strlen(program.cookie_prefix) + 1 +
1429 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1430 return "[COOKIE ERROR]"; /* should never happen */
1432 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1433 program.cookie_prefix, file_type,
1434 program.version_major, program.version_minor);
1439 int getFileVersionFromCookieString(const char *cookie)
1441 const char *ptr_cookie1, *ptr_cookie2;
1442 const char *pattern1 = "_FILE_VERSION_";
1443 const char *pattern2 = "?.?";
1444 const int len_cookie = strlen(cookie);
1445 const int len_pattern1 = strlen(pattern1);
1446 const int len_pattern2 = strlen(pattern2);
1447 const int len_pattern = len_pattern1 + len_pattern2;
1448 int version_major, version_minor;
1450 if (len_cookie <= len_pattern)
1453 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1454 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1456 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1459 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1460 ptr_cookie2[1] != '.' ||
1461 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1464 version_major = ptr_cookie2[0] - '0';
1465 version_minor = ptr_cookie2[2] - '0';
1467 return VERSION_IDENT(version_major, version_minor, 0, 0);
1470 boolean checkCookieString(const char *cookie, const char *template)
1472 const char *pattern = "_FILE_VERSION_?.?";
1473 const int len_cookie = strlen(cookie);
1474 const int len_template = strlen(template);
1475 const int len_pattern = strlen(pattern);
1477 if (len_cookie != len_template)
1480 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1486 /* ------------------------------------------------------------------------- */
1487 /* setup file list and hash handling functions */
1488 /* ------------------------------------------------------------------------- */
1490 char *getFormattedSetupEntry(char *token, char *value)
1493 static char entry[MAX_LINE_LEN];
1495 /* if value is an empty string, just return token without value */
1499 /* start with the token and some spaces to format output line */
1500 sprintf(entry, "%s:", token);
1501 for (i = strlen(entry); i < token_value_position; i++)
1504 /* continue with the token's value */
1505 strcat(entry, value);
1510 SetupFileList *newSetupFileList(char *token, char *value)
1512 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1514 new->token = getStringCopy(token);
1515 new->value = getStringCopy(value);
1522 void freeSetupFileList(SetupFileList *list)
1527 checked_free(list->token);
1528 checked_free(list->value);
1531 freeSetupFileList(list->next);
1536 char *getListEntry(SetupFileList *list, char *token)
1541 if (strEqual(list->token, token))
1544 return getListEntry(list->next, token);
1547 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1552 if (strEqual(list->token, token))
1554 checked_free(list->value);
1556 list->value = getStringCopy(value);
1560 else if (list->next == NULL)
1561 return (list->next = newSetupFileList(token, value));
1563 return setListEntry(list->next, token, value);
1566 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1571 if (list->next == NULL)
1572 return (list->next = newSetupFileList(token, value));
1574 return addListEntry(list->next, token, value);
1578 static void printSetupFileList(SetupFileList *list)
1583 printf("token: '%s'\n", list->token);
1584 printf("value: '%s'\n", list->value);
1586 printSetupFileList(list->next);
1591 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1592 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1593 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1594 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1596 #define insert_hash_entry hashtable_insert
1597 #define search_hash_entry hashtable_search
1598 #define change_hash_entry hashtable_change
1599 #define remove_hash_entry hashtable_remove
1602 static unsigned int get_hash_from_key(void *key)
1607 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1608 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1609 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1610 it works better than many other constants, prime or not) has never been
1611 adequately explained.
1613 If you just want to have a good hash function, and cannot wait, djb2
1614 is one of the best string hash functions i know. It has excellent
1615 distribution and speed on many different sets of keys and table sizes.
1616 You are not likely to do better with one of the "well known" functions
1617 such as PJW, K&R, etc.
1619 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1622 char *str = (char *)key;
1623 unsigned int hash = 5381;
1626 while ((c = *str++))
1627 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1632 static int keys_are_equal(void *key1, void *key2)
1634 return (strEqual((char *)key1, (char *)key2));
1637 SetupFileHash *newSetupFileHash()
1639 SetupFileHash *new_hash =
1640 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1642 if (new_hash == NULL)
1643 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1648 void freeSetupFileHash(SetupFileHash *hash)
1653 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1656 char *getHashEntry(SetupFileHash *hash, char *token)
1661 return search_hash_entry(hash, token);
1664 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1671 value_copy = getStringCopy(value);
1673 /* change value; if it does not exist, insert it as new */
1674 if (!change_hash_entry(hash, token, value_copy))
1675 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1676 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1679 char *removeHashEntry(SetupFileHash *hash, char *token)
1684 return remove_hash_entry(hash, token);
1688 static void printSetupFileHash(SetupFileHash *hash)
1690 BEGIN_HASH_ITERATION(hash, itr)
1692 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1693 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1695 END_HASH_ITERATION(hash, itr)
1699 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1700 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1701 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1703 static boolean token_value_separator_found = FALSE;
1704 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1705 static boolean token_value_separator_warning = FALSE;
1707 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1708 static boolean token_already_exists_warning = FALSE;
1711 static boolean getTokenValueFromSetupLineExt(char *line,
1712 char **token_ptr, char **value_ptr,
1713 char *filename, char *line_raw,
1715 boolean separator_required)
1717 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1718 char *token, *value, *line_ptr;
1720 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1721 if (line_raw == NULL)
1723 strncpy(line_copy, line, MAX_LINE_LEN);
1724 line_copy[MAX_LINE_LEN] = '\0';
1727 strcpy(line_raw_copy, line_copy);
1728 line_raw = line_raw_copy;
1731 /* cut trailing comment from input line */
1732 for (line_ptr = line; *line_ptr; line_ptr++)
1734 if (*line_ptr == '#')
1741 /* cut trailing whitespaces from input line */
1742 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1743 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1746 /* ignore empty lines */
1750 /* cut leading whitespaces from token */
1751 for (token = line; *token; token++)
1752 if (*token != ' ' && *token != '\t')
1755 /* start with empty value as reliable default */
1758 token_value_separator_found = FALSE;
1760 /* find end of token to determine start of value */
1761 for (line_ptr = token; *line_ptr; line_ptr++)
1764 /* first look for an explicit token/value separator, like ':' or '=' */
1765 if (*line_ptr == ':' || *line_ptr == '=')
1767 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1770 *line_ptr = '\0'; /* terminate token string */
1771 value = line_ptr + 1; /* set beginning of value */
1773 token_value_separator_found = TRUE;
1779 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1780 /* fallback: if no token/value separator found, also allow whitespaces */
1781 if (!token_value_separator_found && !separator_required)
1783 for (line_ptr = token; *line_ptr; line_ptr++)
1785 if (*line_ptr == ' ' || *line_ptr == '\t')
1787 *line_ptr = '\0'; /* terminate token string */
1788 value = line_ptr + 1; /* set beginning of value */
1790 token_value_separator_found = TRUE;
1796 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1797 if (token_value_separator_found)
1799 if (!token_value_separator_warning)
1801 Error(ERR_INFO_LINE, "-");
1803 if (filename != NULL)
1805 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1806 Error(ERR_INFO, "- config file: '%s'", filename);
1810 Error(ERR_WARN, "missing token/value separator(s):");
1813 token_value_separator_warning = TRUE;
1816 if (filename != NULL)
1817 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1819 Error(ERR_INFO, "- line: '%s'", line_raw);
1825 /* cut trailing whitespaces from token */
1826 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1827 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1830 /* cut leading whitespaces from value */
1831 for (; *value; value++)
1832 if (*value != ' ' && *value != '\t')
1837 value = "true"; /* treat tokens without value as "true" */
1846 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1848 /* while the internal (old) interface does not require a token/value
1849 separator (for downwards compatibility with existing files which
1850 don't use them), it is mandatory for the external (new) interface */
1852 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1856 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1857 boolean top_recursion_level, boolean is_hash)
1859 static SetupFileHash *include_filename_hash = NULL;
1860 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1861 char *token, *value, *line_ptr;
1862 void *insert_ptr = NULL;
1863 boolean read_continued_line = FALSE;
1865 int line_nr = 0, token_count = 0, include_count = 0;
1867 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1868 token_value_separator_warning = FALSE;
1871 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1872 token_already_exists_warning = FALSE;
1875 if (!(file = fopen(filename, MODE_READ)))
1877 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1882 /* use "insert pointer" to store list end for constant insertion complexity */
1884 insert_ptr = setup_file_data;
1886 /* on top invocation, create hash to mark included files (to prevent loops) */
1887 if (top_recursion_level)
1888 include_filename_hash = newSetupFileHash();
1890 /* mark this file as already included (to prevent including it again) */
1891 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1895 /* read next line of input file */
1896 if (!fgets(line, MAX_LINE_LEN, file))
1899 /* check if line was completely read and is terminated by line break */
1900 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1903 /* cut trailing line break (this can be newline and/or carriage return) */
1904 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1905 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1908 /* copy raw input line for later use (mainly debugging output) */
1909 strcpy(line_raw, line);
1911 if (read_continued_line)
1914 /* !!! ??? WHY ??? !!! */
1915 /* cut leading whitespaces from input line */
1916 for (line_ptr = line; *line_ptr; line_ptr++)
1917 if (*line_ptr != ' ' && *line_ptr != '\t')
1921 /* append new line to existing line, if there is enough space */
1922 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1923 strcat(previous_line, line_ptr);
1925 strcpy(line, previous_line); /* copy storage buffer to line */
1927 read_continued_line = FALSE;
1930 /* if the last character is '\', continue at next line */
1931 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1933 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1934 strcpy(previous_line, line); /* copy line to storage buffer */
1936 read_continued_line = TRUE;
1941 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1942 line_raw, line_nr, FALSE))
1947 if (strEqual(token, "include"))
1949 if (getHashEntry(include_filename_hash, value) == NULL)
1951 char *basepath = getBasePath(filename);
1952 char *basename = getBaseName(value);
1953 char *filename_include = getPath2(basepath, basename);
1956 Error(ERR_INFO, "[including file '%s']", filename_include);
1959 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1963 free(filename_include);
1969 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1976 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1978 getHashEntry((SetupFileHash *)setup_file_data, token);
1980 if (old_value != NULL)
1982 if (!token_already_exists_warning)
1984 Error(ERR_INFO_LINE, "-");
1985 Error(ERR_WARN, "duplicate token(s) found in config file:");
1986 Error(ERR_INFO, "- config file: '%s'", filename);
1988 token_already_exists_warning = TRUE;
1991 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
1992 Error(ERR_INFO, " old value: '%s'", old_value);
1993 Error(ERR_INFO, " new value: '%s'", value);
1997 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2001 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2011 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2012 if (token_value_separator_warning)
2013 Error(ERR_INFO_LINE, "-");
2016 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2017 if (token_already_exists_warning)
2018 Error(ERR_INFO_LINE, "-");
2021 if (token_count == 0 && include_count == 0)
2022 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2024 if (top_recursion_level)
2025 freeSetupFileHash(include_filename_hash);
2032 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2033 boolean top_recursion_level, boolean is_hash)
2035 static SetupFileHash *include_filename_hash = NULL;
2036 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2037 char *token, *value, *line_ptr;
2038 void *insert_ptr = NULL;
2039 boolean read_continued_line = FALSE;
2042 int token_count = 0;
2044 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2045 token_value_separator_warning = FALSE;
2048 if (!(file = fopen(filename, MODE_READ)))
2050 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2055 /* use "insert pointer" to store list end for constant insertion complexity */
2057 insert_ptr = setup_file_data;
2059 /* on top invocation, create hash to mark included files (to prevent loops) */
2060 if (top_recursion_level)
2061 include_filename_hash = newSetupFileHash();
2063 /* mark this file as already included (to prevent including it again) */
2064 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2068 /* read next line of input file */
2069 if (!fgets(line, MAX_LINE_LEN, file))
2072 /* check if line was completely read and is terminated by line break */
2073 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2076 /* cut trailing line break (this can be newline and/or carriage return) */
2077 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2078 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2081 /* copy raw input line for later use (mainly debugging output) */
2082 strcpy(line_raw, line);
2084 if (read_continued_line)
2086 /* cut leading whitespaces from input line */
2087 for (line_ptr = line; *line_ptr; line_ptr++)
2088 if (*line_ptr != ' ' && *line_ptr != '\t')
2091 /* append new line to existing line, if there is enough space */
2092 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2093 strcat(previous_line, line_ptr);
2095 strcpy(line, previous_line); /* copy storage buffer to line */
2097 read_continued_line = FALSE;
2100 /* if the last character is '\', continue at next line */
2101 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2103 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2104 strcpy(previous_line, line); /* copy line to storage buffer */
2106 read_continued_line = TRUE;
2111 /* cut trailing comment from input line */
2112 for (line_ptr = line; *line_ptr; line_ptr++)
2114 if (*line_ptr == '#')
2121 /* cut trailing whitespaces from input line */
2122 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2123 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2126 /* ignore empty lines */
2130 /* cut leading whitespaces from token */
2131 for (token = line; *token; token++)
2132 if (*token != ' ' && *token != '\t')
2135 /* start with empty value as reliable default */
2138 token_value_separator_found = FALSE;
2140 /* find end of token to determine start of value */
2141 for (line_ptr = token; *line_ptr; line_ptr++)
2144 /* first look for an explicit token/value separator, like ':' or '=' */
2145 if (*line_ptr == ':' || *line_ptr == '=')
2147 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2150 *line_ptr = '\0'; /* terminate token string */
2151 value = line_ptr + 1; /* set beginning of value */
2153 token_value_separator_found = TRUE;
2159 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2160 /* fallback: if no token/value separator found, also allow whitespaces */
2161 if (!token_value_separator_found)
2163 for (line_ptr = token; *line_ptr; line_ptr++)
2165 if (*line_ptr == ' ' || *line_ptr == '\t')
2167 *line_ptr = '\0'; /* terminate token string */
2168 value = line_ptr + 1; /* set beginning of value */
2170 token_value_separator_found = TRUE;
2176 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2177 if (token_value_separator_found)
2179 if (!token_value_separator_warning)
2181 Error(ERR_INFO_LINE, "-");
2182 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2183 Error(ERR_INFO, "- config file: '%s'", filename);
2185 token_value_separator_warning = TRUE;
2188 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2194 /* cut trailing whitespaces from token */
2195 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2196 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2199 /* cut leading whitespaces from value */
2200 for (; *value; value++)
2201 if (*value != ' ' && *value != '\t')
2206 value = "true"; /* treat tokens without value as "true" */
2211 if (strEqual(token, "include"))
2213 if (getHashEntry(include_filename_hash, value) == NULL)
2215 char *basepath = getBasePath(filename);
2216 char *basename = getBaseName(value);
2217 char *filename_include = getPath2(basepath, basename);
2220 Error(ERR_INFO, "[including file '%s']", filename_include);
2223 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2227 free(filename_include);
2231 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2237 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2239 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2248 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2249 if (token_value_separator_warning)
2250 Error(ERR_INFO_LINE, "-");
2253 if (token_count == 0)
2254 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2256 if (top_recursion_level)
2257 freeSetupFileHash(include_filename_hash);
2263 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2267 if (!(file = fopen(filename, MODE_WRITE)))
2269 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2274 BEGIN_HASH_ITERATION(hash, itr)
2276 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2277 HASH_ITERATION_VALUE(itr)));
2279 END_HASH_ITERATION(hash, itr)
2284 SetupFileList *loadSetupFileList(char *filename)
2286 SetupFileList *setup_file_list = newSetupFileList("", "");
2287 SetupFileList *first_valid_list_entry;
2289 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2291 freeSetupFileList(setup_file_list);
2296 first_valid_list_entry = setup_file_list->next;
2298 /* free empty list header */
2299 setup_file_list->next = NULL;
2300 freeSetupFileList(setup_file_list);
2302 return first_valid_list_entry;
2305 SetupFileHash *loadSetupFileHash(char *filename)
2307 SetupFileHash *setup_file_hash = newSetupFileHash();
2309 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2311 freeSetupFileHash(setup_file_hash);
2316 return setup_file_hash;
2319 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2320 char *filename, char *identifier)
2322 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2325 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2326 else if (!checkCookieString(value, identifier))
2327 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2331 /* ========================================================================= */
2332 /* setup file stuff */
2333 /* ========================================================================= */
2335 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2336 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2337 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2339 /* level directory info */
2340 #define LEVELINFO_TOKEN_IDENTIFIER 0
2341 #define LEVELINFO_TOKEN_NAME 1
2342 #define LEVELINFO_TOKEN_NAME_SORTING 2
2343 #define LEVELINFO_TOKEN_AUTHOR 3
2344 #define LEVELINFO_TOKEN_YEAR 4
2345 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2346 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2347 #define LEVELINFO_TOKEN_TESTED_BY 7
2348 #define LEVELINFO_TOKEN_LEVELS 8
2349 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2350 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2351 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2352 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2353 #define LEVELINFO_TOKEN_READONLY 13
2354 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2355 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2356 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2357 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2358 #define LEVELINFO_TOKEN_MUSIC_SET 18
2359 #define LEVELINFO_TOKEN_FILENAME 19
2360 #define LEVELINFO_TOKEN_FILETYPE 20
2361 #define LEVELINFO_TOKEN_HANDICAP 21
2362 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2364 #define NUM_LEVELINFO_TOKENS 23
2366 static LevelDirTree ldi;
2368 static struct TokenInfo levelinfo_tokens[] =
2370 /* level directory info */
2371 { TYPE_STRING, &ldi.identifier, "identifier" },
2372 { TYPE_STRING, &ldi.name, "name" },
2373 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2374 { TYPE_STRING, &ldi.author, "author" },
2375 { TYPE_STRING, &ldi.year, "year" },
2376 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2377 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2378 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2379 { TYPE_INTEGER, &ldi.levels, "levels" },
2380 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2381 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2382 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2383 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2384 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2385 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2386 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2387 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2388 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2389 { TYPE_STRING, &ldi.music_set, "music_set" },
2390 { TYPE_STRING, &ldi.level_filename, "filename" },
2391 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2392 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2393 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2396 static struct TokenInfo artworkinfo_tokens[] =
2398 /* artwork directory info */
2399 { TYPE_STRING, &ldi.identifier, "identifier" },
2400 { TYPE_STRING, &ldi.subdir, "subdir" },
2401 { TYPE_STRING, &ldi.name, "name" },
2402 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2403 { TYPE_STRING, &ldi.author, "author" },
2404 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2405 { TYPE_STRING, &ldi.basepath, "basepath" },
2406 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2407 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2408 { TYPE_INTEGER, &ldi.color, "color" },
2409 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2414 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2418 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2419 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2420 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2421 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2424 ti->node_parent = NULL;
2425 ti->node_group = NULL;
2432 ti->fullpath = NULL;
2433 ti->basepath = NULL;
2434 ti->identifier = NULL;
2435 ti->name = getStringCopy(ANONYMOUS_NAME);
2436 ti->name_sorting = NULL;
2437 ti->author = getStringCopy(ANONYMOUS_NAME);
2440 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2441 ti->latest_engine = FALSE; /* default: get from level */
2442 ti->parent_link = FALSE;
2443 ti->in_user_dir = FALSE;
2444 ti->user_defined = FALSE;
2446 ti->class_desc = NULL;
2448 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2450 if (ti->type == TREE_TYPE_LEVEL_DIR)
2452 ti->imported_from = NULL;
2453 ti->imported_by = NULL;
2454 ti->tested_by = NULL;
2456 ti->graphics_set_ecs = NULL;
2457 ti->graphics_set_aga = NULL;
2458 ti->graphics_set = NULL;
2459 ti->sounds_set = NULL;
2460 ti->music_set = NULL;
2461 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2462 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2463 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2465 ti->level_filename = NULL;
2466 ti->level_filetype = NULL;
2469 ti->first_level = 0;
2471 ti->level_group = FALSE;
2472 ti->handicap_level = 0;
2473 ti->readonly = TRUE;
2474 ti->handicap = TRUE;
2475 ti->skip_levels = FALSE;
2479 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2483 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2485 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2490 /* copy all values from the parent structure */
2492 ti->type = parent->type;
2494 ti->node_top = parent->node_top;
2495 ti->node_parent = parent;
2496 ti->node_group = NULL;
2503 ti->fullpath = NULL;
2504 ti->basepath = NULL;
2505 ti->identifier = NULL;
2506 ti->name = getStringCopy(ANONYMOUS_NAME);
2507 ti->name_sorting = NULL;
2508 ti->author = getStringCopy(parent->author);
2509 ti->year = getStringCopy(parent->year);
2511 ti->sort_priority = parent->sort_priority;
2512 ti->latest_engine = parent->latest_engine;
2513 ti->parent_link = FALSE;
2514 ti->in_user_dir = parent->in_user_dir;
2515 ti->user_defined = parent->user_defined;
2516 ti->color = parent->color;
2517 ti->class_desc = getStringCopy(parent->class_desc);
2519 ti->infotext = getStringCopy(parent->infotext);
2521 if (ti->type == TREE_TYPE_LEVEL_DIR)
2523 ti->imported_from = getStringCopy(parent->imported_from);
2524 ti->imported_by = getStringCopy(parent->imported_by);
2525 ti->tested_by = getStringCopy(parent->tested_by);
2527 ti->graphics_set_ecs = NULL;
2528 ti->graphics_set_aga = NULL;
2529 ti->graphics_set = NULL;
2530 ti->sounds_set = NULL;
2531 ti->music_set = NULL;
2532 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2533 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2534 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2536 ti->level_filename = NULL;
2537 ti->level_filetype = NULL;
2540 ti->first_level = 0;
2542 ti->level_group = FALSE;
2543 ti->handicap_level = 0;
2544 ti->readonly = TRUE;
2545 ti->handicap = TRUE;
2546 ti->skip_levels = FALSE;
2550 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2552 TreeInfo *ti_copy = newTreeInfo();
2554 /* copy all values from the original structure */
2556 ti_copy->type = ti->type;
2558 ti_copy->node_top = ti->node_top;
2559 ti_copy->node_parent = ti->node_parent;
2560 ti_copy->node_group = ti->node_group;
2561 ti_copy->next = ti->next;
2563 ti_copy->cl_first = ti->cl_first;
2564 ti_copy->cl_cursor = ti->cl_cursor;
2566 ti_copy->subdir = getStringCopy(ti->subdir);
2567 ti_copy->fullpath = getStringCopy(ti->fullpath);
2568 ti_copy->basepath = getStringCopy(ti->basepath);
2569 ti_copy->identifier = getStringCopy(ti->identifier);
2570 ti_copy->name = getStringCopy(ti->name);
2571 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2572 ti_copy->author = getStringCopy(ti->author);
2573 ti_copy->year = getStringCopy(ti->year);
2574 ti_copy->imported_from = getStringCopy(ti->imported_from);
2575 ti_copy->imported_by = getStringCopy(ti->imported_by);
2576 ti_copy->tested_by = getStringCopy(ti->tested_by);
2578 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2579 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2580 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2581 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2582 ti_copy->music_set = getStringCopy(ti->music_set);
2583 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2584 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2585 ti_copy->music_path = getStringCopy(ti->music_path);
2587 ti_copy->level_filename = getStringCopy(ti->level_filename);
2588 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2590 ti_copy->levels = ti->levels;
2591 ti_copy->first_level = ti->first_level;
2592 ti_copy->last_level = ti->last_level;
2593 ti_copy->sort_priority = ti->sort_priority;
2595 ti_copy->latest_engine = ti->latest_engine;
2597 ti_copy->level_group = ti->level_group;
2598 ti_copy->parent_link = ti->parent_link;
2599 ti_copy->in_user_dir = ti->in_user_dir;
2600 ti_copy->user_defined = ti->user_defined;
2601 ti_copy->readonly = ti->readonly;
2602 ti_copy->handicap = ti->handicap;
2603 ti_copy->skip_levels = ti->skip_levels;
2605 ti_copy->color = ti->color;
2606 ti_copy->class_desc = getStringCopy(ti->class_desc);
2607 ti_copy->handicap_level = ti->handicap_level;
2609 ti_copy->infotext = getStringCopy(ti->infotext);
2614 static void freeTreeInfo(TreeInfo *ti)
2619 checked_free(ti->subdir);
2620 checked_free(ti->fullpath);
2621 checked_free(ti->basepath);
2622 checked_free(ti->identifier);
2624 checked_free(ti->name);
2625 checked_free(ti->name_sorting);
2626 checked_free(ti->author);
2627 checked_free(ti->year);
2629 checked_free(ti->class_desc);
2631 checked_free(ti->infotext);
2633 if (ti->type == TREE_TYPE_LEVEL_DIR)
2635 checked_free(ti->imported_from);
2636 checked_free(ti->imported_by);
2637 checked_free(ti->tested_by);
2639 checked_free(ti->graphics_set_ecs);
2640 checked_free(ti->graphics_set_aga);
2641 checked_free(ti->graphics_set);
2642 checked_free(ti->sounds_set);
2643 checked_free(ti->music_set);
2645 checked_free(ti->graphics_path);
2646 checked_free(ti->sounds_path);
2647 checked_free(ti->music_path);
2649 checked_free(ti->level_filename);
2650 checked_free(ti->level_filetype);
2656 void setSetupInfo(struct TokenInfo *token_info,
2657 int token_nr, char *token_value)
2659 int token_type = token_info[token_nr].type;
2660 void *setup_value = token_info[token_nr].value;
2662 if (token_value == NULL)
2665 /* set setup field to corresponding token value */
2670 *(boolean *)setup_value = get_boolean_from_string(token_value);
2674 *(Key *)setup_value = getKeyFromKeyName(token_value);
2678 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2682 *(int *)setup_value = get_integer_from_string(token_value);
2686 checked_free(*(char **)setup_value);
2687 *(char **)setup_value = getStringCopy(token_value);
2695 static int compareTreeInfoEntries(const void *object1, const void *object2)
2697 const TreeInfo *entry1 = *((TreeInfo **)object1);
2698 const TreeInfo *entry2 = *((TreeInfo **)object2);
2699 int class_sorting1, class_sorting2;
2702 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2704 class_sorting1 = LEVELSORTING(entry1);
2705 class_sorting2 = LEVELSORTING(entry2);
2709 class_sorting1 = ARTWORKSORTING(entry1);
2710 class_sorting2 = ARTWORKSORTING(entry2);
2713 if (entry1->parent_link || entry2->parent_link)
2714 compare_result = (entry1->parent_link ? -1 : +1);
2715 else if (entry1->sort_priority == entry2->sort_priority)
2717 char *name1 = getStringToLower(entry1->name_sorting);
2718 char *name2 = getStringToLower(entry2->name_sorting);
2720 compare_result = strcmp(name1, name2);
2725 else if (class_sorting1 == class_sorting2)
2726 compare_result = entry1->sort_priority - entry2->sort_priority;
2728 compare_result = class_sorting1 - class_sorting2;
2730 return compare_result;
2733 static void createParentTreeInfoNode(TreeInfo *node_parent)
2737 if (node_parent == NULL)
2740 ti_new = newTreeInfo();
2741 setTreeInfoToDefaults(ti_new, node_parent->type);
2743 ti_new->node_parent = node_parent;
2744 ti_new->parent_link = TRUE;
2746 setString(&ti_new->identifier, node_parent->identifier);
2747 setString(&ti_new->name, ".. (parent directory)");
2748 setString(&ti_new->name_sorting, ti_new->name);
2750 setString(&ti_new->subdir, "..");
2751 setString(&ti_new->fullpath, node_parent->fullpath);
2753 ti_new->sort_priority = node_parent->sort_priority;
2754 ti_new->latest_engine = node_parent->latest_engine;
2756 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2758 pushTreeInfo(&node_parent->node_group, ti_new);
2762 /* -------------------------------------------------------------------------- */
2763 /* functions for handling level and custom artwork info cache */
2764 /* -------------------------------------------------------------------------- */
2766 static void LoadArtworkInfoCache()
2768 InitCacheDirectory();
2770 if (artworkinfo_cache_old == NULL)
2772 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2774 /* try to load artwork info hash from already existing cache file */
2775 artworkinfo_cache_old = loadSetupFileHash(filename);
2777 /* if no artwork info cache file was found, start with empty hash */
2778 if (artworkinfo_cache_old == NULL)
2779 artworkinfo_cache_old = newSetupFileHash();
2784 if (artworkinfo_cache_new == NULL)
2785 artworkinfo_cache_new = newSetupFileHash();
2788 static void SaveArtworkInfoCache()
2790 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2792 InitCacheDirectory();
2794 saveSetupFileHash(artworkinfo_cache_new, filename);
2799 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2801 static char *prefix = NULL;
2803 checked_free(prefix);
2805 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2810 /* (identical to above function, but separate string buffer needed -- nasty) */
2811 static char *getCacheToken(char *prefix, char *suffix)
2813 static char *token = NULL;
2815 checked_free(token);
2817 token = getStringCat2WithSeparator(prefix, suffix, ".");
2822 static char *getFileTimestamp(char *filename)
2824 struct stat file_status;
2826 if (stat(filename, &file_status) != 0) /* cannot stat file */
2827 return getStringCopy(i_to_a(0));
2829 return getStringCopy(i_to_a(file_status.st_mtime));
2832 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2834 struct stat file_status;
2836 if (timestamp_string == NULL)
2839 if (stat(filename, &file_status) != 0) /* cannot stat file */
2842 return (file_status.st_mtime != atoi(timestamp_string));
2845 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2847 char *identifier = level_node->subdir;
2848 char *type_string = ARTWORK_DIRECTORY(type);
2849 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2850 char *token_main = getCacheToken(token_prefix, "CACHED");
2851 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2852 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2853 TreeInfo *artwork_info = NULL;
2855 if (!use_artworkinfo_cache)
2862 artwork_info = newTreeInfo();
2863 setTreeInfoToDefaults(artwork_info, type);
2865 /* set all structure fields according to the token/value pairs */
2866 ldi = *artwork_info;
2867 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2869 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2870 char *value = getHashEntry(artworkinfo_cache_old, token);
2872 setSetupInfo(artworkinfo_tokens, i, value);
2874 /* check if cache entry for this item is invalid or incomplete */
2878 Error(ERR_WARN, "cache entry '%s' invalid", token);
2885 *artwork_info = ldi;
2890 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2891 LEVELINFO_FILENAME);
2892 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2893 ARTWORKINFO_FILENAME(type));
2895 /* check if corresponding "levelinfo.conf" file has changed */
2896 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2897 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2899 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2902 /* check if corresponding "<artworkinfo>.conf" file has changed */
2903 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2904 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2906 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2911 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2914 checked_free(filename_levelinfo);
2915 checked_free(filename_artworkinfo);
2918 if (!cached && artwork_info != NULL)
2920 freeTreeInfo(artwork_info);
2925 return artwork_info;
2928 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2929 LevelDirTree *level_node, int type)
2931 char *identifier = level_node->subdir;
2932 char *type_string = ARTWORK_DIRECTORY(type);
2933 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2934 char *token_main = getCacheToken(token_prefix, "CACHED");
2935 boolean set_cache_timestamps = TRUE;
2938 setHashEntry(artworkinfo_cache_new, token_main, "true");
2940 if (set_cache_timestamps)
2942 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2943 LEVELINFO_FILENAME);
2944 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2945 ARTWORKINFO_FILENAME(type));
2946 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2947 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2949 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2950 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2952 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2953 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2955 checked_free(filename_levelinfo);
2956 checked_free(filename_artworkinfo);
2957 checked_free(timestamp_levelinfo);
2958 checked_free(timestamp_artworkinfo);
2961 ldi = *artwork_info;
2962 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2964 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2965 char *value = getSetupValue(artworkinfo_tokens[i].type,
2966 artworkinfo_tokens[i].value);
2968 setHashEntry(artworkinfo_cache_new, token, value);
2973 /* -------------------------------------------------------------------------- */
2974 /* functions for loading level info and custom artwork info */
2975 /* -------------------------------------------------------------------------- */
2977 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2978 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2980 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2981 TreeInfo *node_parent,
2982 char *level_directory,
2983 char *directory_name)
2986 static unsigned long progress_delay = 0;
2987 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2989 char *directory_path = getPath2(level_directory, directory_name);
2990 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2991 SetupFileHash *setup_file_hash;
2992 LevelDirTree *leveldir_new = NULL;
2995 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2996 if (!options.debug && !fileExists(filename))
2998 free(directory_path);
3004 setup_file_hash = loadSetupFileHash(filename);
3006 if (setup_file_hash == NULL)
3008 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3010 free(directory_path);
3016 leveldir_new = newTreeInfo();
3019 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3021 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3023 leveldir_new->subdir = getStringCopy(directory_name);
3025 checkSetupFileHashIdentifier(setup_file_hash, filename,
3026 getCookie("LEVELINFO"));
3028 /* set all structure fields according to the token/value pairs */
3029 ldi = *leveldir_new;
3030 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3031 setSetupInfo(levelinfo_tokens, i,
3032 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3033 *leveldir_new = ldi;
3035 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3036 setString(&leveldir_new->name, leveldir_new->subdir);
3038 if (leveldir_new->identifier == NULL)
3039 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3041 if (leveldir_new->name_sorting == NULL)
3042 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3044 if (node_parent == NULL) /* top level group */
3046 leveldir_new->basepath = getStringCopy(level_directory);
3047 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3049 else /* sub level group */
3051 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3052 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3056 if (leveldir_new->levels < 1)
3057 leveldir_new->levels = 1;
3060 leveldir_new->last_level =
3061 leveldir_new->first_level + leveldir_new->levels - 1;
3063 leveldir_new->in_user_dir =
3064 (!strEqual(leveldir_new->basepath, options.level_directory));
3066 /* adjust some settings if user's private level directory was detected */
3067 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3068 leveldir_new->in_user_dir &&
3069 (strEqual(leveldir_new->subdir, getLoginName()) ||
3070 strEqual(leveldir_new->name, getLoginName()) ||
3071 strEqual(leveldir_new->author, getRealName())))
3073 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3074 leveldir_new->readonly = FALSE;
3077 leveldir_new->user_defined =
3078 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3080 leveldir_new->color = LEVELCOLOR(leveldir_new);
3082 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3084 leveldir_new->handicap_level = /* set handicap to default value */
3085 (leveldir_new->user_defined || !leveldir_new->handicap ?
3086 leveldir_new->last_level : leveldir_new->first_level);
3090 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3091 leveldir_new->level_group);
3093 if (leveldir_new->level_group ||
3094 DelayReached(&progress_delay, progress_delay_value))
3095 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3098 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3102 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3104 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3106 /* skip level sets without levels (which are probably artwork base sets) */
3108 freeSetupFileHash(setup_file_hash);
3109 free(directory_path);
3117 pushTreeInfo(node_first, leveldir_new);
3119 freeSetupFileHash(setup_file_hash);
3121 if (leveldir_new->level_group)
3123 /* create node to link back to current level directory */
3124 createParentTreeInfoNode(leveldir_new);
3126 /* recursively step into sub-directory and look for more level series */
3127 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3128 leveldir_new, directory_path);
3131 free(directory_path);
3137 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3138 TreeInfo *node_parent,
3139 char *level_directory)
3142 struct dirent *dir_entry;
3143 boolean valid_entry_found = FALSE;
3145 if ((dir = opendir(level_directory)) == NULL)
3147 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3151 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3153 struct stat file_status;
3154 char *directory_name = dir_entry->d_name;
3155 char *directory_path = getPath2(level_directory, directory_name);
3157 /* skip entries for current and parent directory */
3158 if (strEqual(directory_name, ".") ||
3159 strEqual(directory_name, ".."))
3161 free(directory_path);
3165 /* find out if directory entry is itself a directory */
3166 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3167 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3169 free(directory_path);
3173 free(directory_path);
3175 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3176 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3177 strEqual(directory_name, MUSIC_DIRECTORY))
3180 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3187 /* special case: top level directory may directly contain "levelinfo.conf" */
3188 if (node_parent == NULL && !valid_entry_found)
3190 /* check if this directory directly contains a file "levelinfo.conf" */
3191 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3192 level_directory, ".");
3195 if (!valid_entry_found)
3196 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3200 boolean AdjustGraphicsForEMC()
3202 boolean settings_changed = FALSE;
3204 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3205 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3207 return settings_changed;
3210 void LoadLevelInfo()
3212 InitUserLevelDirectory(getLoginName());
3214 DrawInitText("Loading level series", 120, FC_GREEN);
3216 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3217 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3219 /* after loading all level set information, clone the level directory tree
3220 and remove all level sets without levels (these may still contain artwork
3221 to be offered in the setup menu as "custom artwork", and are therefore
3222 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3223 leveldir_first_all = leveldir_first;
3224 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3226 AdjustGraphicsForEMC();
3228 /* before sorting, the first entries will be from the user directory */
3229 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3231 if (leveldir_first == NULL)
3232 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3234 sortTreeInfo(&leveldir_first);
3237 dumpTreeInfo(leveldir_first, 0);
3241 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3242 TreeInfo *node_parent,
3243 char *base_directory,
3244 char *directory_name, int type)
3246 char *directory_path = getPath2(base_directory, directory_name);
3247 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3248 SetupFileHash *setup_file_hash = NULL;
3249 TreeInfo *artwork_new = NULL;
3252 if (fileExists(filename))
3253 setup_file_hash = loadSetupFileHash(filename);
3255 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3258 struct dirent *dir_entry;
3259 boolean valid_file_found = FALSE;
3261 if ((dir = opendir(directory_path)) != NULL)
3263 while ((dir_entry = readdir(dir)) != NULL)
3265 char *entry_name = dir_entry->d_name;
3267 if (FileIsArtworkType(entry_name, type))
3269 valid_file_found = TRUE;
3277 if (!valid_file_found)
3279 if (!strEqual(directory_name, "."))
3280 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3282 free(directory_path);
3289 artwork_new = newTreeInfo();
3292 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3294 setTreeInfoToDefaults(artwork_new, type);
3296 artwork_new->subdir = getStringCopy(directory_name);
3298 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3301 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3304 /* set all structure fields according to the token/value pairs */
3306 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3307 setSetupInfo(levelinfo_tokens, i,
3308 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3311 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3312 setString(&artwork_new->name, artwork_new->subdir);
3314 if (artwork_new->identifier == NULL)
3315 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3317 if (artwork_new->name_sorting == NULL)
3318 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3321 if (node_parent == NULL) /* top level group */
3323 artwork_new->basepath = getStringCopy(base_directory);
3324 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3326 else /* sub level group */
3328 artwork_new->basepath = getStringCopy(node_parent->basepath);
3329 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3332 artwork_new->in_user_dir =
3333 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3335 /* (may use ".sort_priority" from "setup_file_hash" above) */
3336 artwork_new->color = ARTWORKCOLOR(artwork_new);
3338 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3340 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3342 if (strEqual(artwork_new->subdir, "."))
3344 if (artwork_new->user_defined)
3346 setString(&artwork_new->identifier, "private");
3347 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3351 setString(&artwork_new->identifier, "classic");
3352 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3355 /* set to new values after changing ".sort_priority" */
3356 artwork_new->color = ARTWORKCOLOR(artwork_new);
3358 setString(&artwork_new->class_desc,
3359 getLevelClassDescription(artwork_new));
3363 setString(&artwork_new->identifier, artwork_new->subdir);
3366 setString(&artwork_new->name, artwork_new->identifier);
3367 setString(&artwork_new->name_sorting, artwork_new->name);
3371 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3374 pushTreeInfo(node_first, artwork_new);
3376 freeSetupFileHash(setup_file_hash);
3378 free(directory_path);
3384 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3385 TreeInfo *node_parent,
3386 char *base_directory, int type)
3389 struct dirent *dir_entry;
3390 boolean valid_entry_found = FALSE;
3392 if ((dir = opendir(base_directory)) == NULL)
3394 /* display error if directory is main "options.graphics_directory" etc. */
3395 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3396 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3401 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3403 struct stat file_status;
3404 char *directory_name = dir_entry->d_name;
3405 char *directory_path = getPath2(base_directory, directory_name);
3407 /* skip directory entries for current and parent directory */
3408 if (strEqual(directory_name, ".") ||
3409 strEqual(directory_name, ".."))
3411 free(directory_path);
3415 /* skip directory entries which are not a directory or are not accessible */
3416 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3417 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3419 free(directory_path);
3423 free(directory_path);
3425 /* check if this directory contains artwork with or without config file */
3426 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3428 directory_name, type);
3433 /* check if this directory directly contains artwork itself */
3434 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3435 base_directory, ".",
3437 if (!valid_entry_found)
3438 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3442 static TreeInfo *getDummyArtworkInfo(int type)
3444 /* this is only needed when there is completely no artwork available */
3445 TreeInfo *artwork_new = newTreeInfo();
3447 setTreeInfoToDefaults(artwork_new, type);
3449 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3450 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3451 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3453 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3454 setString(&artwork_new->name, UNDEFINED_FILENAME);
3455 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3460 void LoadArtworkInfo()
3462 LoadArtworkInfoCache();
3464 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3466 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3467 options.graphics_directory,
3468 TREE_TYPE_GRAPHICS_DIR);
3469 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3470 getUserGraphicsDir(),
3471 TREE_TYPE_GRAPHICS_DIR);
3473 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3474 options.sounds_directory,
3475 TREE_TYPE_SOUNDS_DIR);
3476 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3478 TREE_TYPE_SOUNDS_DIR);
3480 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3481 options.music_directory,
3482 TREE_TYPE_MUSIC_DIR);
3483 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3485 TREE_TYPE_MUSIC_DIR);
3487 if (artwork.gfx_first == NULL)
3488 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3489 if (artwork.snd_first == NULL)
3490 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3491 if (artwork.mus_first == NULL)
3492 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3494 /* before sorting, the first entries will be from the user directory */
3495 artwork.gfx_current =
3496 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3497 if (artwork.gfx_current == NULL)
3498 artwork.gfx_current =
3499 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3500 if (artwork.gfx_current == NULL)
3501 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3503 artwork.snd_current =
3504 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3505 if (artwork.snd_current == NULL)
3506 artwork.snd_current =
3507 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3508 if (artwork.snd_current == NULL)
3509 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3511 artwork.mus_current =
3512 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3513 if (artwork.mus_current == NULL)
3514 artwork.mus_current =
3515 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3516 if (artwork.mus_current == NULL)
3517 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3519 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3520 artwork.snd_current_identifier = artwork.snd_current->identifier;
3521 artwork.mus_current_identifier = artwork.mus_current->identifier;
3524 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3525 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3526 printf("music set == %s\n\n", artwork.mus_current_identifier);
3529 sortTreeInfo(&artwork.gfx_first);
3530 sortTreeInfo(&artwork.snd_first);
3531 sortTreeInfo(&artwork.mus_first);
3534 dumpTreeInfo(artwork.gfx_first, 0);
3535 dumpTreeInfo(artwork.snd_first, 0);
3536 dumpTreeInfo(artwork.mus_first, 0);
3540 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3541 LevelDirTree *level_node)
3544 static unsigned long progress_delay = 0;
3545 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3547 int type = (*artwork_node)->type;
3549 /* recursively check all level directories for artwork sub-directories */
3553 /* check all tree entries for artwork, but skip parent link entries */
3554 if (!level_node->parent_link)
3556 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3557 boolean cached = (artwork_new != NULL);
3561 pushTreeInfo(artwork_node, artwork_new);
3565 TreeInfo *topnode_last = *artwork_node;
3566 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3567 ARTWORK_DIRECTORY(type));
3569 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3571 if (topnode_last != *artwork_node) /* check for newly added node */
3573 artwork_new = *artwork_node;
3575 setString(&artwork_new->identifier, level_node->subdir);
3576 setString(&artwork_new->name, level_node->name);
3577 setString(&artwork_new->name_sorting, level_node->name_sorting);
3579 artwork_new->sort_priority = level_node->sort_priority;
3580 artwork_new->color = LEVELCOLOR(artwork_new);
3586 /* insert artwork info (from old cache or filesystem) into new cache */
3587 if (artwork_new != NULL)
3588 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3592 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3593 level_node->level_group);
3595 if (level_node->level_group ||
3596 DelayReached(&progress_delay, progress_delay_value))
3597 DrawInitText(level_node->name, 150, FC_YELLOW);
3600 if (level_node->node_group != NULL)
3601 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3603 level_node = level_node->next;
3607 void LoadLevelArtworkInfo()
3609 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3611 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3612 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3613 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3615 SaveArtworkInfoCache();
3617 /* needed for reloading level artwork not known at ealier stage */
3619 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3621 artwork.gfx_current =
3622 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3623 if (artwork.gfx_current == NULL)
3624 artwork.gfx_current =
3625 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3626 if (artwork.gfx_current == NULL)
3627 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3630 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3632 artwork.snd_current =
3633 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3634 if (artwork.snd_current == NULL)
3635 artwork.snd_current =
3636 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3637 if (artwork.snd_current == NULL)
3638 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3641 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3643 artwork.mus_current =
3644 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3645 if (artwork.mus_current == NULL)
3646 artwork.mus_current =
3647 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3648 if (artwork.mus_current == NULL)
3649 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3652 sortTreeInfo(&artwork.gfx_first);
3653 sortTreeInfo(&artwork.snd_first);
3654 sortTreeInfo(&artwork.mus_first);
3657 dumpTreeInfo(artwork.gfx_first, 0);
3658 dumpTreeInfo(artwork.snd_first, 0);
3659 dumpTreeInfo(artwork.mus_first, 0);
3663 static void SaveUserLevelInfo()
3665 LevelDirTree *level_info;
3670 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3672 if (!(file = fopen(filename, MODE_WRITE)))
3674 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3679 level_info = newTreeInfo();
3681 /* always start with reliable default values */
3682 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3684 setString(&level_info->name, getLoginName());
3685 setString(&level_info->author, getRealName());
3686 level_info->levels = 100;
3687 level_info->first_level = 1;
3689 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3691 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3692 getCookie("LEVELINFO")));
3695 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3697 if (i == LEVELINFO_TOKEN_NAME ||
3698 i == LEVELINFO_TOKEN_AUTHOR ||
3699 i == LEVELINFO_TOKEN_LEVELS ||
3700 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3701 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3703 /* just to make things nicer :) */
3704 if (i == LEVELINFO_TOKEN_AUTHOR)
3705 fprintf(file, "\n");
3708 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3712 SetFilePermissions(filename, PERMS_PRIVATE);
3714 freeTreeInfo(level_info);
3718 char *getSetupValue(int type, void *value)
3720 static char value_string[MAX_LINE_LEN];
3728 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3732 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3736 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3740 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3744 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3748 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3752 sprintf(value_string, "%d", *(int *)value);
3756 if (*(char **)value == NULL)
3759 strcpy(value_string, *(char **)value);
3763 value_string[0] = '\0';
3767 if (type & TYPE_GHOSTED)
3768 strcpy(value_string, "n/a");
3770 return value_string;
3773 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3777 static char token_string[MAX_LINE_LEN];
3778 int token_type = token_info[token_nr].type;
3779 void *setup_value = token_info[token_nr].value;
3780 char *token_text = token_info[token_nr].text;
3781 char *value_string = getSetupValue(token_type, setup_value);
3783 /* build complete token string */
3784 sprintf(token_string, "%s%s", prefix, token_text);
3786 /* build setup entry line */
3787 line = getFormattedSetupEntry(token_string, value_string);
3789 if (token_type == TYPE_KEY_X11)
3791 Key key = *(Key *)setup_value;
3792 char *keyname = getKeyNameFromKey(key);
3794 /* add comment, if useful */
3795 if (!strEqual(keyname, "(undefined)") &&
3796 !strEqual(keyname, "(unknown)"))
3798 /* add at least one whitespace */
3800 for (i = strlen(line); i < token_comment_position; i++)
3804 strcat(line, keyname);
3811 void LoadLevelSetup_LastSeries()
3813 /* ----------------------------------------------------------------------- */
3814 /* ~/.<program>/levelsetup.conf */
3815 /* ----------------------------------------------------------------------- */
3817 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3818 SetupFileHash *level_setup_hash = NULL;
3820 /* always start with reliable default values */
3821 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3823 if ((level_setup_hash = loadSetupFileHash(filename)))
3825 char *last_level_series =
3826 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3828 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3830 if (leveldir_current == NULL)
3831 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3833 checkSetupFileHashIdentifier(level_setup_hash, filename,
3834 getCookie("LEVELSETUP"));
3836 freeSetupFileHash(level_setup_hash);
3839 Error(ERR_WARN, "using default setup values");
3844 void SaveLevelSetup_LastSeries()
3846 /* ----------------------------------------------------------------------- */
3847 /* ~/.<program>/levelsetup.conf */
3848 /* ----------------------------------------------------------------------- */
3850 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3851 char *level_subdir = leveldir_current->subdir;
3854 InitUserDataDirectory();
3856 if (!(file = fopen(filename, MODE_WRITE)))
3858 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3863 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3864 getCookie("LEVELSETUP")));
3865 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3870 SetFilePermissions(filename, PERMS_PRIVATE);
3875 static void checkSeriesInfo()
3877 static char *level_directory = NULL;
3879 struct dirent *dir_entry;
3881 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3883 level_directory = getPath2((leveldir_current->in_user_dir ?
3884 getUserLevelDir(NULL) :
3885 options.level_directory),
3886 leveldir_current->fullpath);
3888 if ((dir = opendir(level_directory)) == NULL)
3890 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3894 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3896 if (strlen(dir_entry->d_name) > 4 &&
3897 dir_entry->d_name[3] == '.' &&
3898 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3900 char levelnum_str[4];
3903 strncpy(levelnum_str, dir_entry->d_name, 3);
3904 levelnum_str[3] = '\0';
3906 levelnum_value = atoi(levelnum_str);
3909 if (levelnum_value < leveldir_current->first_level)
3911 Error(ERR_WARN, "additional level %d found", levelnum_value);
3912 leveldir_current->first_level = levelnum_value;
3914 else if (levelnum_value > leveldir_current->last_level)
3916 Error(ERR_WARN, "additional level %d found", levelnum_value);
3917 leveldir_current->last_level = levelnum_value;
3926 void LoadLevelSetup_SeriesInfo()
3929 SetupFileHash *level_setup_hash = NULL;
3930 char *level_subdir = leveldir_current->subdir;
3932 /* always start with reliable default values */
3933 level_nr = leveldir_current->first_level;
3935 checkSeriesInfo(leveldir_current);
3937 /* ----------------------------------------------------------------------- */
3938 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3939 /* ----------------------------------------------------------------------- */
3941 level_subdir = leveldir_current->subdir;
3943 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3945 if ((level_setup_hash = loadSetupFileHash(filename)))
3949 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3953 level_nr = atoi(token_value);
3955 if (level_nr < leveldir_current->first_level)
3956 level_nr = leveldir_current->first_level;
3957 if (level_nr > leveldir_current->last_level)
3958 level_nr = leveldir_current->last_level;
3961 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3965 int level_nr = atoi(token_value);
3967 if (level_nr < leveldir_current->first_level)
3968 level_nr = leveldir_current->first_level;
3969 if (level_nr > leveldir_current->last_level + 1)
3970 level_nr = leveldir_current->last_level;
3972 if (leveldir_current->user_defined || !leveldir_current->handicap)
3973 level_nr = leveldir_current->last_level;
3975 leveldir_current->handicap_level = level_nr;
3978 checkSetupFileHashIdentifier(level_setup_hash, filename,
3979 getCookie("LEVELSETUP"));
3981 freeSetupFileHash(level_setup_hash);
3984 Error(ERR_WARN, "using default setup values");
3989 void SaveLevelSetup_SeriesInfo()
3992 char *level_subdir = leveldir_current->subdir;
3993 char *level_nr_str = int2str(level_nr, 0);
3994 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3997 /* ----------------------------------------------------------------------- */
3998 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3999 /* ----------------------------------------------------------------------- */
4001 InitLevelSetupDirectory(level_subdir);
4003 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4005 if (!(file = fopen(filename, MODE_WRITE)))
4007 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4012 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4013 getCookie("LEVELSETUP")));
4014 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4016 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4017 handicap_level_str));
4021 SetFilePermissions(filename, PERMS_PRIVATE);