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 *getClassicArtworkSet(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 *getClassicArtworkDir(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));
341 No (or non-existing) artwork configured in "levelinfo.conf". This would
342 normally result in using the artwork configured in the setup menu. But
343 if an artwork subdirectory exists (which might contain custom artwork
344 or an artwork configuration file), this level artwork must be treated
345 as relative to the default "classic" artwork, not to the artwork that
346 is currently configured in the setup menu.
348 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
349 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
350 the real "classic" artwork from the original R'n'D (like "gfx_classic").
353 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
355 checked_free(*artwork_set_ptr);
359 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
360 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
364 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
365 *artwork_set_ptr = NULL;
371 return *artwork_set_ptr;
374 inline static char *getLevelArtworkSet(int type)
376 if (leveldir_current == NULL)
379 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
382 inline static char *getLevelArtworkDir(int type)
384 if (leveldir_current == NULL)
385 return UNDEFINED_FILENAME;
387 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
390 char *getTapeFilename(int nr)
392 static char *filename = NULL;
393 char basename[MAX_FILENAME_LEN];
395 checked_free(filename);
397 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
398 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
403 char *getSolutionTapeFilename(int nr)
405 static char *filename = NULL;
406 char basename[MAX_FILENAME_LEN];
408 checked_free(filename);
410 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
411 filename = getPath2(getSolutionTapeDir(), basename);
416 char *getScoreFilename(int nr)
418 static char *filename = NULL;
419 char basename[MAX_FILENAME_LEN];
421 checked_free(filename);
423 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
424 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
429 char *getSetupFilename()
431 static char *filename = NULL;
433 checked_free(filename);
435 filename = getPath2(getSetupDir(), SETUP_FILENAME);
440 char *getEditorSetupFilename()
442 static char *filename = NULL;
444 checked_free(filename);
445 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
447 if (fileExists(filename))
450 checked_free(filename);
451 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
456 char *getHelpAnimFilename()
458 static char *filename = NULL;
460 checked_free(filename);
462 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
467 char *getHelpTextFilename()
469 static char *filename = NULL;
471 checked_free(filename);
473 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
478 char *getLevelSetInfoFilename()
480 static char *filename = NULL;
495 for (i = 0; basenames[i] != NULL; i++)
497 checked_free(filename);
498 filename = getPath2(getCurrentLevelDir(), basenames[i]);
500 if (fileExists(filename))
507 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
509 static char basename[32];
511 sprintf(basename, "%s_%d.txt",
512 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
517 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
519 static char *filename = NULL;
521 boolean skip_setup_artwork = FALSE;
523 checked_free(filename);
525 basename = getLevelSetTitleMessageBasename(nr, initial);
527 if (!gfx.override_level_graphics)
529 /* 1st try: look for special artwork in current level series directory */
530 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
531 if (fileExists(filename))
536 /* 2nd try: look for message file in current level set directory */
537 filename = getPath2(getCurrentLevelDir(), basename);
538 if (fileExists(filename))
543 /* check if there is special artwork configured in level series config */
544 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
546 /* 3rd try: look for special artwork configured in level series config */
547 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
548 if (fileExists(filename))
553 /* take missing artwork configured in level set config from default */
554 skip_setup_artwork = TRUE;
558 if (!skip_setup_artwork)
560 /* 4th try: look for special artwork in configured artwork directory */
561 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
562 if (fileExists(filename))
568 /* 5th try: look for default artwork in new default artwork directory */
569 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
570 if (fileExists(filename))
575 /* 6th try: look for default artwork in old default artwork directory */
576 filename = getPath2(options.graphics_directory, basename);
577 if (fileExists(filename))
580 return NULL; /* cannot find specified artwork file anywhere */
583 static char *getCorrectedArtworkBasename(char *basename)
585 char *basename_corrected = basename;
587 #if defined(PLATFORM_MSDOS)
588 if (program.filename_prefix != NULL)
590 int prefix_len = strlen(program.filename_prefix);
592 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
593 basename_corrected = &basename[prefix_len];
595 /* if corrected filename is still longer than standard MS-DOS filename
596 size (8 characters + 1 dot + 3 characters file extension), shorten
597 filename by writing file extension after 8th basename character */
598 if (strlen(basename_corrected) > 8 + 1 + 3)
600 static char *msdos_filename = NULL;
602 checked_free(msdos_filename);
604 msdos_filename = getStringCopy(basename_corrected);
605 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
607 basename_corrected = msdos_filename;
612 return basename_corrected;
615 char *getCustomImageFilename(char *basename)
617 static char *filename = NULL;
618 boolean skip_setup_artwork = FALSE;
620 checked_free(filename);
622 basename = getCorrectedArtworkBasename(basename);
624 if (!gfx.override_level_graphics)
626 /* 1st try: look for special artwork in current level series directory */
627 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
628 if (fileExists(filename))
633 /* check if there is special artwork configured in level series config */
634 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
636 /* 2nd try: look for special artwork configured in level series config */
637 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
638 if (fileExists(filename))
643 /* take missing artwork configured in level set config from default */
644 skip_setup_artwork = TRUE;
648 if (!skip_setup_artwork)
650 /* 3rd try: look for special artwork in configured artwork directory */
651 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
652 if (fileExists(filename))
658 /* 4th try: look for default artwork in new default artwork directory */
659 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
660 if (fileExists(filename))
665 /* 5th try: look for default artwork in old default artwork directory */
666 filename = getPath2(options.graphics_directory, basename);
667 if (fileExists(filename))
670 #if defined(CREATE_SPECIAL_EDITION)
673 /* !!! INSERT WARNING HERE TO REPORT MISSING ARTWORK FILES !!! */
675 printf("::: MISSING ARTWORK FILE '%s'\n", basename);
678 /* 6th try: look for fallback artwork in old default artwork directory */
679 /* (needed to prevent errors when trying to access unused artwork files) */
680 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
681 if (fileExists(filename))
685 return NULL; /* cannot find specified artwork file anywhere */
688 char *getCustomSoundFilename(char *basename)
690 static char *filename = NULL;
691 boolean skip_setup_artwork = FALSE;
693 checked_free(filename);
695 basename = getCorrectedArtworkBasename(basename);
697 if (!gfx.override_level_sounds)
699 /* 1st try: look for special artwork in current level series directory */
700 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
701 if (fileExists(filename))
706 /* check if there is special artwork configured in level series config */
707 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
709 /* 2nd try: look for special artwork configured in level series config */
710 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
711 if (fileExists(filename))
716 /* take missing artwork configured in level set config from default */
717 skip_setup_artwork = TRUE;
721 if (!skip_setup_artwork)
723 /* 3rd try: look for special artwork in configured artwork directory */
724 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
725 if (fileExists(filename))
731 /* 4th try: look for default artwork in new default artwork directory */
732 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
733 if (fileExists(filename))
738 /* 5th try: look for default artwork in old default artwork directory */
739 filename = getPath2(options.sounds_directory, basename);
740 if (fileExists(filename))
743 #if defined(CREATE_SPECIAL_EDITION)
746 /* 6th try: look for fallback artwork in old default artwork directory */
747 /* (needed to prevent errors when trying to access unused artwork files) */
748 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
749 if (fileExists(filename))
753 return NULL; /* cannot find specified artwork file anywhere */
756 char *getCustomMusicFilename(char *basename)
758 static char *filename = NULL;
759 boolean skip_setup_artwork = FALSE;
761 checked_free(filename);
763 basename = getCorrectedArtworkBasename(basename);
765 if (!gfx.override_level_music)
767 /* 1st try: look for special artwork in current level series directory */
768 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
769 if (fileExists(filename))
774 /* check if there is special artwork configured in level series config */
775 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
777 /* 2nd try: look for special artwork configured in level series config */
778 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
779 if (fileExists(filename))
784 /* take missing artwork configured in level set config from default */
785 skip_setup_artwork = TRUE;
789 if (!skip_setup_artwork)
791 /* 3rd try: look for special artwork in configured artwork directory */
792 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
793 if (fileExists(filename))
799 /* 4th try: look for default artwork in new default artwork directory */
800 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
801 if (fileExists(filename))
806 /* 5th try: look for default artwork in old default artwork directory */
807 filename = getPath2(options.music_directory, basename);
808 if (fileExists(filename))
811 #if defined(CREATE_SPECIAL_EDITION)
814 /* 6th try: look for fallback artwork in old default artwork directory */
815 /* (needed to prevent errors when trying to access unused artwork files) */
816 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
817 if (fileExists(filename))
821 return NULL; /* cannot find specified artwork file anywhere */
824 char *getCustomArtworkFilename(char *basename, int type)
826 if (type == ARTWORK_TYPE_GRAPHICS)
827 return getCustomImageFilename(basename);
828 else if (type == ARTWORK_TYPE_SOUNDS)
829 return getCustomSoundFilename(basename);
830 else if (type == ARTWORK_TYPE_MUSIC)
831 return getCustomMusicFilename(basename);
833 return UNDEFINED_FILENAME;
836 char *getCustomArtworkConfigFilename(int type)
838 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
841 char *getCustomArtworkLevelConfigFilename(int type)
843 static char *filename = NULL;
845 checked_free(filename);
847 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
852 char *getCustomMusicDirectory(void)
854 static char *directory = NULL;
855 boolean skip_setup_artwork = FALSE;
857 checked_free(directory);
859 if (!gfx.override_level_music)
861 /* 1st try: look for special artwork in current level series directory */
862 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
863 if (fileExists(directory))
868 /* check if there is special artwork configured in level series config */
869 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
871 /* 2nd try: look for special artwork configured in level series config */
872 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
873 if (fileExists(directory))
878 /* take missing artwork configured in level set config from default */
879 skip_setup_artwork = TRUE;
883 if (!skip_setup_artwork)
885 /* 3rd try: look for special artwork in configured artwork directory */
886 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
887 if (fileExists(directory))
893 /* 4th try: look for default artwork in new default artwork directory */
894 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
895 if (fileExists(directory))
900 /* 5th try: look for default artwork in old default artwork directory */
901 directory = getStringCopy(options.music_directory);
902 if (fileExists(directory))
905 return NULL; /* cannot find specified artwork file anywhere */
908 void InitTapeDirectory(char *level_subdir)
910 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
911 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
912 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
915 void InitScoreDirectory(char *level_subdir)
917 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
918 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
919 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
922 static void SaveUserLevelInfo();
924 void InitUserLevelDirectory(char *level_subdir)
926 if (!fileExists(getUserLevelDir(level_subdir)))
928 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
929 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
930 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
936 void InitLevelSetupDirectory(char *level_subdir)
938 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
939 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
940 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
943 void InitCacheDirectory()
945 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
946 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
950 /* ------------------------------------------------------------------------- */
951 /* some functions to handle lists of level and artwork directories */
952 /* ------------------------------------------------------------------------- */
954 TreeInfo *newTreeInfo()
956 return checked_calloc(sizeof(TreeInfo));
959 TreeInfo *newTreeInfo_setDefaults(int type)
961 TreeInfo *ti = newTreeInfo();
963 setTreeInfoToDefaults(ti, type);
968 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
970 node_new->next = *node_first;
971 *node_first = node_new;
974 int numTreeInfo(TreeInfo *node)
987 boolean validLevelSeries(TreeInfo *node)
989 return (node != NULL && !node->node_group && !node->parent_link);
992 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
997 if (node->node_group) /* enter level group (step down into tree) */
998 return getFirstValidTreeInfoEntry(node->node_group);
999 else if (node->parent_link) /* skip start entry of level group */
1001 if (node->next) /* get first real level series entry */
1002 return getFirstValidTreeInfoEntry(node->next);
1003 else /* leave empty level group and go on */
1004 return getFirstValidTreeInfoEntry(node->node_parent->next);
1006 else /* this seems to be a regular level series */
1010 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1015 if (node->node_parent == NULL) /* top level group */
1016 return *node->node_top;
1017 else /* sub level group */
1018 return node->node_parent->node_group;
1021 int numTreeInfoInGroup(TreeInfo *node)
1023 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1026 int posTreeInfo(TreeInfo *node)
1028 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1033 if (node_cmp == node)
1037 node_cmp = node_cmp->next;
1043 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1045 TreeInfo *node_default = node;
1057 return node_default;
1060 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1062 if (identifier == NULL)
1067 if (node->node_group)
1069 TreeInfo *node_group;
1071 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1076 else if (!node->parent_link)
1078 if (strEqual(identifier, node->identifier))
1088 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1089 TreeInfo *node, boolean skip_sets_without_levels)
1096 if (!node->parent_link && !node->level_group &&
1097 skip_sets_without_levels && node->levels == 0)
1098 return cloneTreeNode(node_top, node_parent, node->next,
1099 skip_sets_without_levels);
1102 node_new = getTreeInfoCopy(node); /* copy complete node */
1104 node_new = newTreeInfo();
1106 *node_new = *node; /* copy complete node */
1109 node_new->node_top = node_top; /* correct top node link */
1110 node_new->node_parent = node_parent; /* correct parent node link */
1112 if (node->level_group)
1113 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1114 skip_sets_without_levels);
1116 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1117 skip_sets_without_levels);
1122 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1124 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1126 *ti_new = ti_cloned;
1129 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1131 boolean settings_changed = FALSE;
1135 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1136 !strEqual(node->graphics_set, node->graphics_set_ecs))
1138 setString(&node->graphics_set, node->graphics_set_ecs);
1139 settings_changed = TRUE;
1141 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1142 !strEqual(node->graphics_set, node->graphics_set_aga))
1144 setString(&node->graphics_set, node->graphics_set_aga);
1145 settings_changed = TRUE;
1148 if (node->node_group != NULL)
1149 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1154 return settings_changed;
1157 void dumpTreeInfo(TreeInfo *node, int depth)
1161 printf("Dumping TreeInfo:\n");
1165 for (i = 0; i < (depth + 1) * 3; i++)
1168 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1169 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1171 if (node->node_group != NULL)
1172 dumpTreeInfo(node->node_group, depth + 1);
1178 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1179 int (*compare_function)(const void *,
1182 int num_nodes = numTreeInfo(*node_first);
1183 TreeInfo **sort_array;
1184 TreeInfo *node = *node_first;
1190 /* allocate array for sorting structure pointers */
1191 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1193 /* writing structure pointers to sorting array */
1194 while (i < num_nodes && node) /* double boundary check... */
1196 sort_array[i] = node;
1202 /* sorting the structure pointers in the sorting array */
1203 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1206 /* update the linkage of list elements with the sorted node array */
1207 for (i = 0; i < num_nodes - 1; i++)
1208 sort_array[i]->next = sort_array[i + 1];
1209 sort_array[num_nodes - 1]->next = NULL;
1211 /* update the linkage of the main list anchor pointer */
1212 *node_first = sort_array[0];
1216 /* now recursively sort the level group structures */
1220 if (node->node_group != NULL)
1221 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1227 void sortTreeInfo(TreeInfo **node_first)
1229 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1233 /* ========================================================================= */
1234 /* some stuff from "files.c" */
1235 /* ========================================================================= */
1237 #if defined(PLATFORM_WIN32)
1239 #define S_IRGRP S_IRUSR
1242 #define S_IROTH S_IRUSR
1245 #define S_IWGRP S_IWUSR
1248 #define S_IWOTH S_IWUSR
1251 #define S_IXGRP S_IXUSR
1254 #define S_IXOTH S_IXUSR
1257 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1262 #endif /* PLATFORM_WIN32 */
1264 /* file permissions for newly written files */
1265 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1266 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1267 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1269 #define MODE_W_PRIVATE (S_IWUSR)
1270 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1271 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1273 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1274 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1276 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1277 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1281 static char *dir = NULL;
1283 #if defined(PLATFORM_WIN32)
1286 dir = checked_malloc(MAX_PATH + 1);
1288 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1291 #elif defined(PLATFORM_UNIX)
1294 if ((dir = getenv("HOME")) == NULL)
1298 if ((pwd = getpwuid(getuid())) != NULL)
1299 dir = getStringCopy(pwd->pw_dir);
1311 char *getCommonDataDir(void)
1313 static char *common_data_dir = NULL;
1315 #if defined(PLATFORM_WIN32)
1316 if (common_data_dir == NULL)
1318 char *dir = checked_malloc(MAX_PATH + 1);
1320 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1321 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1322 common_data_dir = getPath2(dir, program.userdata_subdir);
1324 common_data_dir = options.rw_base_directory;
1327 if (common_data_dir == NULL)
1328 common_data_dir = options.rw_base_directory;
1331 return common_data_dir;
1334 char *getPersonalDataDir(void)
1336 static char *personal_data_dir = NULL;
1338 #if defined(PLATFORM_MACOSX)
1339 if (personal_data_dir == NULL)
1340 personal_data_dir = getPath2(getHomeDir(), "Documents");
1342 if (personal_data_dir == NULL)
1343 personal_data_dir = getHomeDir();
1346 return personal_data_dir;
1349 char *getUserGameDataDir(void)
1351 static char *user_game_data_dir = NULL;
1353 if (user_game_data_dir == NULL)
1354 user_game_data_dir = getPath2(getPersonalDataDir(),
1355 program.userdata_subdir);
1357 return user_game_data_dir;
1360 void updateUserGameDataDir()
1362 #if defined(PLATFORM_MACOSX)
1363 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1364 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1366 /* convert old Unix style game data directory to Mac OS X style, if needed */
1367 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1369 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1371 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1372 userdata_dir_old, userdata_dir_new);
1374 /* continue using Unix style data directory -- this should not happen */
1375 program.userdata_path = getPath2(getPersonalDataDir(),
1376 program.userdata_subdir_unix);
1380 free(userdata_dir_old);
1386 return getUserGameDataDir();
1389 static mode_t posix_umask(mode_t mask)
1391 #if defined(PLATFORM_UNIX)
1398 static int posix_mkdir(const char *pathname, mode_t mode)
1400 #if defined(PLATFORM_WIN32)
1401 return mkdir(pathname);
1403 return mkdir(pathname, mode);
1407 void createDirectory(char *dir, char *text, int permission_class)
1409 /* leave "other" permissions in umask untouched, but ensure group parts
1410 of USERDATA_DIR_MODE are not masked */
1411 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1412 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1413 mode_t normal_umask = posix_umask(0);
1414 mode_t group_umask = ~(dir_mode & S_IRWXG);
1415 posix_umask(normal_umask & group_umask);
1417 if (!fileExists(dir))
1418 if (posix_mkdir(dir, dir_mode) != 0)
1419 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1421 posix_umask(normal_umask); /* reset normal umask */
1424 void InitUserDataDirectory()
1426 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1429 void SetFilePermissions(char *filename, int permission_class)
1431 chmod(filename, (permission_class == PERMS_PRIVATE ?
1432 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1435 char *getCookie(char *file_type)
1437 static char cookie[MAX_COOKIE_LEN + 1];
1439 if (strlen(program.cookie_prefix) + 1 +
1440 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1441 return "[COOKIE ERROR]"; /* should never happen */
1443 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1444 program.cookie_prefix, file_type,
1445 program.version_major, program.version_minor);
1450 int getFileVersionFromCookieString(const char *cookie)
1452 const char *ptr_cookie1, *ptr_cookie2;
1453 const char *pattern1 = "_FILE_VERSION_";
1454 const char *pattern2 = "?.?";
1455 const int len_cookie = strlen(cookie);
1456 const int len_pattern1 = strlen(pattern1);
1457 const int len_pattern2 = strlen(pattern2);
1458 const int len_pattern = len_pattern1 + len_pattern2;
1459 int version_major, version_minor;
1461 if (len_cookie <= len_pattern)
1464 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1465 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1467 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1470 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1471 ptr_cookie2[1] != '.' ||
1472 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1475 version_major = ptr_cookie2[0] - '0';
1476 version_minor = ptr_cookie2[2] - '0';
1478 return VERSION_IDENT(version_major, version_minor, 0, 0);
1481 boolean checkCookieString(const char *cookie, const char *template)
1483 const char *pattern = "_FILE_VERSION_?.?";
1484 const int len_cookie = strlen(cookie);
1485 const int len_template = strlen(template);
1486 const int len_pattern = strlen(pattern);
1488 if (len_cookie != len_template)
1491 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1497 /* ------------------------------------------------------------------------- */
1498 /* setup file list and hash handling functions */
1499 /* ------------------------------------------------------------------------- */
1501 char *getFormattedSetupEntry(char *token, char *value)
1504 static char entry[MAX_LINE_LEN];
1506 /* if value is an empty string, just return token without value */
1510 /* start with the token and some spaces to format output line */
1511 sprintf(entry, "%s:", token);
1512 for (i = strlen(entry); i < token_value_position; i++)
1515 /* continue with the token's value */
1516 strcat(entry, value);
1521 SetupFileList *newSetupFileList(char *token, char *value)
1523 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1525 new->token = getStringCopy(token);
1526 new->value = getStringCopy(value);
1533 void freeSetupFileList(SetupFileList *list)
1538 checked_free(list->token);
1539 checked_free(list->value);
1542 freeSetupFileList(list->next);
1547 char *getListEntry(SetupFileList *list, char *token)
1552 if (strEqual(list->token, token))
1555 return getListEntry(list->next, token);
1558 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1563 if (strEqual(list->token, token))
1565 checked_free(list->value);
1567 list->value = getStringCopy(value);
1571 else if (list->next == NULL)
1572 return (list->next = newSetupFileList(token, value));
1574 return setListEntry(list->next, token, value);
1577 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1582 if (list->next == NULL)
1583 return (list->next = newSetupFileList(token, value));
1585 return addListEntry(list->next, token, value);
1589 static void printSetupFileList(SetupFileList *list)
1594 printf("token: '%s'\n", list->token);
1595 printf("value: '%s'\n", list->value);
1597 printSetupFileList(list->next);
1602 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1603 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1604 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1605 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1607 #define insert_hash_entry hashtable_insert
1608 #define search_hash_entry hashtable_search
1609 #define change_hash_entry hashtable_change
1610 #define remove_hash_entry hashtable_remove
1613 static unsigned int get_hash_from_key(void *key)
1618 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1619 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1620 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1621 it works better than many other constants, prime or not) has never been
1622 adequately explained.
1624 If you just want to have a good hash function, and cannot wait, djb2
1625 is one of the best string hash functions i know. It has excellent
1626 distribution and speed on many different sets of keys and table sizes.
1627 You are not likely to do better with one of the "well known" functions
1628 such as PJW, K&R, etc.
1630 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1633 char *str = (char *)key;
1634 unsigned int hash = 5381;
1637 while ((c = *str++))
1638 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1643 static int keys_are_equal(void *key1, void *key2)
1645 return (strEqual((char *)key1, (char *)key2));
1648 SetupFileHash *newSetupFileHash()
1650 SetupFileHash *new_hash =
1651 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1653 if (new_hash == NULL)
1654 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1659 void freeSetupFileHash(SetupFileHash *hash)
1664 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1667 char *getHashEntry(SetupFileHash *hash, char *token)
1672 return search_hash_entry(hash, token);
1675 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1682 value_copy = getStringCopy(value);
1684 /* change value; if it does not exist, insert it as new */
1685 if (!change_hash_entry(hash, token, value_copy))
1686 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1687 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1690 char *removeHashEntry(SetupFileHash *hash, char *token)
1695 return remove_hash_entry(hash, token);
1699 static void printSetupFileHash(SetupFileHash *hash)
1701 BEGIN_HASH_ITERATION(hash, itr)
1703 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1704 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1706 END_HASH_ITERATION(hash, itr)
1710 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1711 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1712 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1714 static boolean token_value_separator_found = FALSE;
1715 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1716 static boolean token_value_separator_warning = FALSE;
1718 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1719 static boolean token_already_exists_warning = FALSE;
1722 static boolean getTokenValueFromSetupLineExt(char *line,
1723 char **token_ptr, char **value_ptr,
1724 char *filename, char *line_raw,
1726 boolean separator_required)
1728 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1729 char *token, *value, *line_ptr;
1731 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1732 if (line_raw == NULL)
1734 strncpy(line_copy, line, MAX_LINE_LEN);
1735 line_copy[MAX_LINE_LEN] = '\0';
1738 strcpy(line_raw_copy, line_copy);
1739 line_raw = line_raw_copy;
1742 /* cut trailing comment from input line */
1743 for (line_ptr = line; *line_ptr; line_ptr++)
1745 if (*line_ptr == '#')
1752 /* cut trailing whitespaces from input line */
1753 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1754 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1757 /* ignore empty lines */
1761 /* cut leading whitespaces from token */
1762 for (token = line; *token; token++)
1763 if (*token != ' ' && *token != '\t')
1766 /* start with empty value as reliable default */
1769 token_value_separator_found = FALSE;
1771 /* find end of token to determine start of value */
1772 for (line_ptr = token; *line_ptr; line_ptr++)
1775 /* first look for an explicit token/value separator, like ':' or '=' */
1776 if (*line_ptr == ':' || *line_ptr == '=')
1778 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1781 *line_ptr = '\0'; /* terminate token string */
1782 value = line_ptr + 1; /* set beginning of value */
1784 token_value_separator_found = TRUE;
1790 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1791 /* fallback: if no token/value separator found, also allow whitespaces */
1792 if (!token_value_separator_found && !separator_required)
1794 for (line_ptr = token; *line_ptr; line_ptr++)
1796 if (*line_ptr == ' ' || *line_ptr == '\t')
1798 *line_ptr = '\0'; /* terminate token string */
1799 value = line_ptr + 1; /* set beginning of value */
1801 token_value_separator_found = TRUE;
1807 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1808 if (token_value_separator_found)
1810 if (!token_value_separator_warning)
1812 Error(ERR_INFO_LINE, "-");
1814 if (filename != NULL)
1816 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1817 Error(ERR_INFO, "- config file: '%s'", filename);
1821 Error(ERR_WARN, "missing token/value separator(s):");
1824 token_value_separator_warning = TRUE;
1827 if (filename != NULL)
1828 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1830 Error(ERR_INFO, "- line: '%s'", line_raw);
1836 /* cut trailing whitespaces from token */
1837 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1838 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1841 /* cut leading whitespaces from value */
1842 for (; *value; value++)
1843 if (*value != ' ' && *value != '\t')
1848 value = "true"; /* treat tokens without value as "true" */
1857 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1859 /* while the internal (old) interface does not require a token/value
1860 separator (for downwards compatibility with existing files which
1861 don't use them), it is mandatory for the external (new) interface */
1863 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1867 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1868 boolean top_recursion_level, boolean is_hash)
1870 static SetupFileHash *include_filename_hash = NULL;
1871 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1872 char *token, *value, *line_ptr;
1873 void *insert_ptr = NULL;
1874 boolean read_continued_line = FALSE;
1876 int line_nr = 0, token_count = 0, include_count = 0;
1878 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1879 token_value_separator_warning = FALSE;
1882 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1883 token_already_exists_warning = FALSE;
1886 if (!(file = fopen(filename, MODE_READ)))
1888 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1893 /* use "insert pointer" to store list end for constant insertion complexity */
1895 insert_ptr = setup_file_data;
1897 /* on top invocation, create hash to mark included files (to prevent loops) */
1898 if (top_recursion_level)
1899 include_filename_hash = newSetupFileHash();
1901 /* mark this file as already included (to prevent including it again) */
1902 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1906 /* read next line of input file */
1907 if (!fgets(line, MAX_LINE_LEN, file))
1910 /* check if line was completely read and is terminated by line break */
1911 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1914 /* cut trailing line break (this can be newline and/or carriage return) */
1915 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1916 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1919 /* copy raw input line for later use (mainly debugging output) */
1920 strcpy(line_raw, line);
1922 if (read_continued_line)
1925 /* !!! ??? WHY ??? !!! */
1926 /* cut leading whitespaces from input line */
1927 for (line_ptr = line; *line_ptr; line_ptr++)
1928 if (*line_ptr != ' ' && *line_ptr != '\t')
1932 /* append new line to existing line, if there is enough space */
1933 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1934 strcat(previous_line, line_ptr);
1936 strcpy(line, previous_line); /* copy storage buffer to line */
1938 read_continued_line = FALSE;
1941 /* if the last character is '\', continue at next line */
1942 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1944 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1945 strcpy(previous_line, line); /* copy line to storage buffer */
1947 read_continued_line = TRUE;
1952 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1953 line_raw, line_nr, FALSE))
1958 if (strEqual(token, "include"))
1960 if (getHashEntry(include_filename_hash, value) == NULL)
1962 char *basepath = getBasePath(filename);
1963 char *basename = getBaseName(value);
1964 char *filename_include = getPath2(basepath, basename);
1967 Error(ERR_INFO, "[including file '%s']", filename_include);
1970 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1974 free(filename_include);
1980 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1987 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1989 getHashEntry((SetupFileHash *)setup_file_data, token);
1991 if (old_value != NULL)
1993 if (!token_already_exists_warning)
1995 Error(ERR_INFO_LINE, "-");
1996 Error(ERR_WARN, "duplicate token(s) found in config file:");
1997 Error(ERR_INFO, "- config file: '%s'", filename);
1999 token_already_exists_warning = TRUE;
2002 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2003 Error(ERR_INFO, " old value: '%s'", old_value);
2004 Error(ERR_INFO, " new value: '%s'", value);
2008 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2012 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2022 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2023 if (token_value_separator_warning)
2024 Error(ERR_INFO_LINE, "-");
2027 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2028 if (token_already_exists_warning)
2029 Error(ERR_INFO_LINE, "-");
2032 if (token_count == 0 && include_count == 0)
2033 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2035 if (top_recursion_level)
2036 freeSetupFileHash(include_filename_hash);
2043 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2044 boolean top_recursion_level, boolean is_hash)
2046 static SetupFileHash *include_filename_hash = NULL;
2047 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2048 char *token, *value, *line_ptr;
2049 void *insert_ptr = NULL;
2050 boolean read_continued_line = FALSE;
2053 int token_count = 0;
2055 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2056 token_value_separator_warning = FALSE;
2059 if (!(file = fopen(filename, MODE_READ)))
2061 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2066 /* use "insert pointer" to store list end for constant insertion complexity */
2068 insert_ptr = setup_file_data;
2070 /* on top invocation, create hash to mark included files (to prevent loops) */
2071 if (top_recursion_level)
2072 include_filename_hash = newSetupFileHash();
2074 /* mark this file as already included (to prevent including it again) */
2075 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2079 /* read next line of input file */
2080 if (!fgets(line, MAX_LINE_LEN, file))
2083 /* check if line was completely read and is terminated by line break */
2084 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2087 /* cut trailing line break (this can be newline and/or carriage return) */
2088 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2089 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2092 /* copy raw input line for later use (mainly debugging output) */
2093 strcpy(line_raw, line);
2095 if (read_continued_line)
2097 /* cut leading whitespaces from input line */
2098 for (line_ptr = line; *line_ptr; line_ptr++)
2099 if (*line_ptr != ' ' && *line_ptr != '\t')
2102 /* append new line to existing line, if there is enough space */
2103 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2104 strcat(previous_line, line_ptr);
2106 strcpy(line, previous_line); /* copy storage buffer to line */
2108 read_continued_line = FALSE;
2111 /* if the last character is '\', continue at next line */
2112 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2114 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2115 strcpy(previous_line, line); /* copy line to storage buffer */
2117 read_continued_line = TRUE;
2122 /* cut trailing comment from input line */
2123 for (line_ptr = line; *line_ptr; line_ptr++)
2125 if (*line_ptr == '#')
2132 /* cut trailing whitespaces from input line */
2133 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2134 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2137 /* ignore empty lines */
2141 /* cut leading whitespaces from token */
2142 for (token = line; *token; token++)
2143 if (*token != ' ' && *token != '\t')
2146 /* start with empty value as reliable default */
2149 token_value_separator_found = FALSE;
2151 /* find end of token to determine start of value */
2152 for (line_ptr = token; *line_ptr; line_ptr++)
2155 /* first look for an explicit token/value separator, like ':' or '=' */
2156 if (*line_ptr == ':' || *line_ptr == '=')
2158 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2161 *line_ptr = '\0'; /* terminate token string */
2162 value = line_ptr + 1; /* set beginning of value */
2164 token_value_separator_found = TRUE;
2170 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2171 /* fallback: if no token/value separator found, also allow whitespaces */
2172 if (!token_value_separator_found)
2174 for (line_ptr = token; *line_ptr; line_ptr++)
2176 if (*line_ptr == ' ' || *line_ptr == '\t')
2178 *line_ptr = '\0'; /* terminate token string */
2179 value = line_ptr + 1; /* set beginning of value */
2181 token_value_separator_found = TRUE;
2187 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2188 if (token_value_separator_found)
2190 if (!token_value_separator_warning)
2192 Error(ERR_INFO_LINE, "-");
2193 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2194 Error(ERR_INFO, "- config file: '%s'", filename);
2196 token_value_separator_warning = TRUE;
2199 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2205 /* cut trailing whitespaces from token */
2206 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2207 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2210 /* cut leading whitespaces from value */
2211 for (; *value; value++)
2212 if (*value != ' ' && *value != '\t')
2217 value = "true"; /* treat tokens without value as "true" */
2222 if (strEqual(token, "include"))
2224 if (getHashEntry(include_filename_hash, value) == NULL)
2226 char *basepath = getBasePath(filename);
2227 char *basename = getBaseName(value);
2228 char *filename_include = getPath2(basepath, basename);
2231 Error(ERR_INFO, "[including file '%s']", filename_include);
2234 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2238 free(filename_include);
2242 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2248 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2250 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2259 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2260 if (token_value_separator_warning)
2261 Error(ERR_INFO_LINE, "-");
2264 if (token_count == 0)
2265 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2267 if (top_recursion_level)
2268 freeSetupFileHash(include_filename_hash);
2274 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2278 if (!(file = fopen(filename, MODE_WRITE)))
2280 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2285 BEGIN_HASH_ITERATION(hash, itr)
2287 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2288 HASH_ITERATION_VALUE(itr)));
2290 END_HASH_ITERATION(hash, itr)
2295 SetupFileList *loadSetupFileList(char *filename)
2297 SetupFileList *setup_file_list = newSetupFileList("", "");
2298 SetupFileList *first_valid_list_entry;
2300 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2302 freeSetupFileList(setup_file_list);
2307 first_valid_list_entry = setup_file_list->next;
2309 /* free empty list header */
2310 setup_file_list->next = NULL;
2311 freeSetupFileList(setup_file_list);
2313 return first_valid_list_entry;
2316 SetupFileHash *loadSetupFileHash(char *filename)
2318 SetupFileHash *setup_file_hash = newSetupFileHash();
2320 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2322 freeSetupFileHash(setup_file_hash);
2327 return setup_file_hash;
2330 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2331 char *filename, char *identifier)
2333 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2336 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2337 else if (!checkCookieString(value, identifier))
2338 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2342 /* ========================================================================= */
2343 /* setup file stuff */
2344 /* ========================================================================= */
2346 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2347 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2348 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2350 /* level directory info */
2351 #define LEVELINFO_TOKEN_IDENTIFIER 0
2352 #define LEVELINFO_TOKEN_NAME 1
2353 #define LEVELINFO_TOKEN_NAME_SORTING 2
2354 #define LEVELINFO_TOKEN_AUTHOR 3
2355 #define LEVELINFO_TOKEN_YEAR 4
2356 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2357 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2358 #define LEVELINFO_TOKEN_TESTED_BY 7
2359 #define LEVELINFO_TOKEN_LEVELS 8
2360 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2361 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2362 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2363 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2364 #define LEVELINFO_TOKEN_READONLY 13
2365 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2366 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2367 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2368 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2369 #define LEVELINFO_TOKEN_MUSIC_SET 18
2370 #define LEVELINFO_TOKEN_FILENAME 19
2371 #define LEVELINFO_TOKEN_FILETYPE 20
2372 #define LEVELINFO_TOKEN_HANDICAP 21
2373 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2375 #define NUM_LEVELINFO_TOKENS 23
2377 static LevelDirTree ldi;
2379 static struct TokenInfo levelinfo_tokens[] =
2381 /* level directory info */
2382 { TYPE_STRING, &ldi.identifier, "identifier" },
2383 { TYPE_STRING, &ldi.name, "name" },
2384 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2385 { TYPE_STRING, &ldi.author, "author" },
2386 { TYPE_STRING, &ldi.year, "year" },
2387 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2388 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2389 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2390 { TYPE_INTEGER, &ldi.levels, "levels" },
2391 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2392 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2393 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2394 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2395 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2396 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2397 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2398 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2399 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2400 { TYPE_STRING, &ldi.music_set, "music_set" },
2401 { TYPE_STRING, &ldi.level_filename, "filename" },
2402 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2403 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2404 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2407 static struct TokenInfo artworkinfo_tokens[] =
2409 /* artwork directory info */
2410 { TYPE_STRING, &ldi.identifier, "identifier" },
2411 { TYPE_STRING, &ldi.subdir, "subdir" },
2412 { TYPE_STRING, &ldi.name, "name" },
2413 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2414 { TYPE_STRING, &ldi.author, "author" },
2415 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2416 { TYPE_STRING, &ldi.basepath, "basepath" },
2417 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2418 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2419 { TYPE_INTEGER, &ldi.color, "color" },
2420 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2425 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2429 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2430 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2431 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2432 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2435 ti->node_parent = NULL;
2436 ti->node_group = NULL;
2443 ti->fullpath = NULL;
2444 ti->basepath = NULL;
2445 ti->identifier = NULL;
2446 ti->name = getStringCopy(ANONYMOUS_NAME);
2447 ti->name_sorting = NULL;
2448 ti->author = getStringCopy(ANONYMOUS_NAME);
2451 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2452 ti->latest_engine = FALSE; /* default: get from level */
2453 ti->parent_link = FALSE;
2454 ti->in_user_dir = FALSE;
2455 ti->user_defined = FALSE;
2457 ti->class_desc = NULL;
2459 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2461 if (ti->type == TREE_TYPE_LEVEL_DIR)
2463 ti->imported_from = NULL;
2464 ti->imported_by = NULL;
2465 ti->tested_by = NULL;
2467 ti->graphics_set_ecs = NULL;
2468 ti->graphics_set_aga = NULL;
2469 ti->graphics_set = NULL;
2470 ti->sounds_set = NULL;
2471 ti->music_set = NULL;
2472 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2473 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2474 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2476 ti->level_filename = NULL;
2477 ti->level_filetype = NULL;
2480 ti->first_level = 0;
2482 ti->level_group = FALSE;
2483 ti->handicap_level = 0;
2484 ti->readonly = TRUE;
2485 ti->handicap = TRUE;
2486 ti->skip_levels = FALSE;
2490 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2494 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2496 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2501 /* copy all values from the parent structure */
2503 ti->type = parent->type;
2505 ti->node_top = parent->node_top;
2506 ti->node_parent = parent;
2507 ti->node_group = NULL;
2514 ti->fullpath = NULL;
2515 ti->basepath = NULL;
2516 ti->identifier = NULL;
2517 ti->name = getStringCopy(ANONYMOUS_NAME);
2518 ti->name_sorting = NULL;
2519 ti->author = getStringCopy(parent->author);
2520 ti->year = getStringCopy(parent->year);
2522 ti->sort_priority = parent->sort_priority;
2523 ti->latest_engine = parent->latest_engine;
2524 ti->parent_link = FALSE;
2525 ti->in_user_dir = parent->in_user_dir;
2526 ti->user_defined = parent->user_defined;
2527 ti->color = parent->color;
2528 ti->class_desc = getStringCopy(parent->class_desc);
2530 ti->infotext = getStringCopy(parent->infotext);
2532 if (ti->type == TREE_TYPE_LEVEL_DIR)
2534 ti->imported_from = getStringCopy(parent->imported_from);
2535 ti->imported_by = getStringCopy(parent->imported_by);
2536 ti->tested_by = getStringCopy(parent->tested_by);
2538 ti->graphics_set_ecs = NULL;
2539 ti->graphics_set_aga = NULL;
2540 ti->graphics_set = NULL;
2541 ti->sounds_set = NULL;
2542 ti->music_set = NULL;
2543 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2544 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2545 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2547 ti->level_filename = NULL;
2548 ti->level_filetype = NULL;
2551 ti->first_level = 0;
2553 ti->level_group = FALSE;
2554 ti->handicap_level = 0;
2555 ti->readonly = TRUE;
2556 ti->handicap = TRUE;
2557 ti->skip_levels = FALSE;
2561 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2563 TreeInfo *ti_copy = newTreeInfo();
2565 /* copy all values from the original structure */
2567 ti_copy->type = ti->type;
2569 ti_copy->node_top = ti->node_top;
2570 ti_copy->node_parent = ti->node_parent;
2571 ti_copy->node_group = ti->node_group;
2572 ti_copy->next = ti->next;
2574 ti_copy->cl_first = ti->cl_first;
2575 ti_copy->cl_cursor = ti->cl_cursor;
2577 ti_copy->subdir = getStringCopy(ti->subdir);
2578 ti_copy->fullpath = getStringCopy(ti->fullpath);
2579 ti_copy->basepath = getStringCopy(ti->basepath);
2580 ti_copy->identifier = getStringCopy(ti->identifier);
2581 ti_copy->name = getStringCopy(ti->name);
2582 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2583 ti_copy->author = getStringCopy(ti->author);
2584 ti_copy->year = getStringCopy(ti->year);
2585 ti_copy->imported_from = getStringCopy(ti->imported_from);
2586 ti_copy->imported_by = getStringCopy(ti->imported_by);
2587 ti_copy->tested_by = getStringCopy(ti->tested_by);
2589 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2590 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2591 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2592 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2593 ti_copy->music_set = getStringCopy(ti->music_set);
2594 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2595 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2596 ti_copy->music_path = getStringCopy(ti->music_path);
2598 ti_copy->level_filename = getStringCopy(ti->level_filename);
2599 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2601 ti_copy->levels = ti->levels;
2602 ti_copy->first_level = ti->first_level;
2603 ti_copy->last_level = ti->last_level;
2604 ti_copy->sort_priority = ti->sort_priority;
2606 ti_copy->latest_engine = ti->latest_engine;
2608 ti_copy->level_group = ti->level_group;
2609 ti_copy->parent_link = ti->parent_link;
2610 ti_copy->in_user_dir = ti->in_user_dir;
2611 ti_copy->user_defined = ti->user_defined;
2612 ti_copy->readonly = ti->readonly;
2613 ti_copy->handicap = ti->handicap;
2614 ti_copy->skip_levels = ti->skip_levels;
2616 ti_copy->color = ti->color;
2617 ti_copy->class_desc = getStringCopy(ti->class_desc);
2618 ti_copy->handicap_level = ti->handicap_level;
2620 ti_copy->infotext = getStringCopy(ti->infotext);
2625 static void freeTreeInfo(TreeInfo *ti)
2630 checked_free(ti->subdir);
2631 checked_free(ti->fullpath);
2632 checked_free(ti->basepath);
2633 checked_free(ti->identifier);
2635 checked_free(ti->name);
2636 checked_free(ti->name_sorting);
2637 checked_free(ti->author);
2638 checked_free(ti->year);
2640 checked_free(ti->class_desc);
2642 checked_free(ti->infotext);
2644 if (ti->type == TREE_TYPE_LEVEL_DIR)
2646 checked_free(ti->imported_from);
2647 checked_free(ti->imported_by);
2648 checked_free(ti->tested_by);
2650 checked_free(ti->graphics_set_ecs);
2651 checked_free(ti->graphics_set_aga);
2652 checked_free(ti->graphics_set);
2653 checked_free(ti->sounds_set);
2654 checked_free(ti->music_set);
2656 checked_free(ti->graphics_path);
2657 checked_free(ti->sounds_path);
2658 checked_free(ti->music_path);
2660 checked_free(ti->level_filename);
2661 checked_free(ti->level_filetype);
2667 void setSetupInfo(struct TokenInfo *token_info,
2668 int token_nr, char *token_value)
2670 int token_type = token_info[token_nr].type;
2671 void *setup_value = token_info[token_nr].value;
2673 if (token_value == NULL)
2676 /* set setup field to corresponding token value */
2681 *(boolean *)setup_value = get_boolean_from_string(token_value);
2685 *(Key *)setup_value = getKeyFromKeyName(token_value);
2689 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2693 *(int *)setup_value = get_integer_from_string(token_value);
2697 checked_free(*(char **)setup_value);
2698 *(char **)setup_value = getStringCopy(token_value);
2706 static int compareTreeInfoEntries(const void *object1, const void *object2)
2708 const TreeInfo *entry1 = *((TreeInfo **)object1);
2709 const TreeInfo *entry2 = *((TreeInfo **)object2);
2710 int class_sorting1, class_sorting2;
2713 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2715 class_sorting1 = LEVELSORTING(entry1);
2716 class_sorting2 = LEVELSORTING(entry2);
2720 class_sorting1 = ARTWORKSORTING(entry1);
2721 class_sorting2 = ARTWORKSORTING(entry2);
2724 if (entry1->parent_link || entry2->parent_link)
2725 compare_result = (entry1->parent_link ? -1 : +1);
2726 else if (entry1->sort_priority == entry2->sort_priority)
2728 char *name1 = getStringToLower(entry1->name_sorting);
2729 char *name2 = getStringToLower(entry2->name_sorting);
2731 compare_result = strcmp(name1, name2);
2736 else if (class_sorting1 == class_sorting2)
2737 compare_result = entry1->sort_priority - entry2->sort_priority;
2739 compare_result = class_sorting1 - class_sorting2;
2741 return compare_result;
2744 static void createParentTreeInfoNode(TreeInfo *node_parent)
2748 if (node_parent == NULL)
2751 ti_new = newTreeInfo();
2752 setTreeInfoToDefaults(ti_new, node_parent->type);
2754 ti_new->node_parent = node_parent;
2755 ti_new->parent_link = TRUE;
2757 setString(&ti_new->identifier, node_parent->identifier);
2758 setString(&ti_new->name, ".. (parent directory)");
2759 setString(&ti_new->name_sorting, ti_new->name);
2761 setString(&ti_new->subdir, "..");
2762 setString(&ti_new->fullpath, node_parent->fullpath);
2764 ti_new->sort_priority = node_parent->sort_priority;
2765 ti_new->latest_engine = node_parent->latest_engine;
2767 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2769 pushTreeInfo(&node_parent->node_group, ti_new);
2773 /* -------------------------------------------------------------------------- */
2774 /* functions for handling level and custom artwork info cache */
2775 /* -------------------------------------------------------------------------- */
2777 static void LoadArtworkInfoCache()
2779 InitCacheDirectory();
2781 if (artworkinfo_cache_old == NULL)
2783 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2785 /* try to load artwork info hash from already existing cache file */
2786 artworkinfo_cache_old = loadSetupFileHash(filename);
2788 /* if no artwork info cache file was found, start with empty hash */
2789 if (artworkinfo_cache_old == NULL)
2790 artworkinfo_cache_old = newSetupFileHash();
2795 if (artworkinfo_cache_new == NULL)
2796 artworkinfo_cache_new = newSetupFileHash();
2799 static void SaveArtworkInfoCache()
2801 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2803 InitCacheDirectory();
2805 saveSetupFileHash(artworkinfo_cache_new, filename);
2810 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2812 static char *prefix = NULL;
2814 checked_free(prefix);
2816 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2821 /* (identical to above function, but separate string buffer needed -- nasty) */
2822 static char *getCacheToken(char *prefix, char *suffix)
2824 static char *token = NULL;
2826 checked_free(token);
2828 token = getStringCat2WithSeparator(prefix, suffix, ".");
2833 static char *getFileTimestamp(char *filename)
2835 struct stat file_status;
2837 if (stat(filename, &file_status) != 0) /* cannot stat file */
2838 return getStringCopy(i_to_a(0));
2840 return getStringCopy(i_to_a(file_status.st_mtime));
2843 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2845 struct stat file_status;
2847 if (timestamp_string == NULL)
2850 if (stat(filename, &file_status) != 0) /* cannot stat file */
2853 return (file_status.st_mtime != atoi(timestamp_string));
2856 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2858 char *identifier = level_node->subdir;
2859 char *type_string = ARTWORK_DIRECTORY(type);
2860 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2861 char *token_main = getCacheToken(token_prefix, "CACHED");
2862 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2863 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2864 TreeInfo *artwork_info = NULL;
2866 if (!use_artworkinfo_cache)
2873 artwork_info = newTreeInfo();
2874 setTreeInfoToDefaults(artwork_info, type);
2876 /* set all structure fields according to the token/value pairs */
2877 ldi = *artwork_info;
2878 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2880 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2881 char *value = getHashEntry(artworkinfo_cache_old, token);
2883 setSetupInfo(artworkinfo_tokens, i, value);
2885 /* check if cache entry for this item is invalid or incomplete */
2889 Error(ERR_WARN, "cache entry '%s' invalid", token);
2896 *artwork_info = ldi;
2901 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2902 LEVELINFO_FILENAME);
2903 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2904 ARTWORKINFO_FILENAME(type));
2906 /* check if corresponding "levelinfo.conf" file has changed */
2907 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2908 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2910 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2913 /* check if corresponding "<artworkinfo>.conf" file has changed */
2914 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2915 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2917 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2922 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2925 checked_free(filename_levelinfo);
2926 checked_free(filename_artworkinfo);
2929 if (!cached && artwork_info != NULL)
2931 freeTreeInfo(artwork_info);
2936 return artwork_info;
2939 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2940 LevelDirTree *level_node, int type)
2942 char *identifier = level_node->subdir;
2943 char *type_string = ARTWORK_DIRECTORY(type);
2944 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2945 char *token_main = getCacheToken(token_prefix, "CACHED");
2946 boolean set_cache_timestamps = TRUE;
2949 setHashEntry(artworkinfo_cache_new, token_main, "true");
2951 if (set_cache_timestamps)
2953 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2954 LEVELINFO_FILENAME);
2955 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2956 ARTWORKINFO_FILENAME(type));
2957 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2958 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2960 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2961 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2963 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2964 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2966 checked_free(filename_levelinfo);
2967 checked_free(filename_artworkinfo);
2968 checked_free(timestamp_levelinfo);
2969 checked_free(timestamp_artworkinfo);
2972 ldi = *artwork_info;
2973 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2975 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2976 char *value = getSetupValue(artworkinfo_tokens[i].type,
2977 artworkinfo_tokens[i].value);
2979 setHashEntry(artworkinfo_cache_new, token, value);
2984 /* -------------------------------------------------------------------------- */
2985 /* functions for loading level info and custom artwork info */
2986 /* -------------------------------------------------------------------------- */
2988 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2989 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2991 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2992 TreeInfo *node_parent,
2993 char *level_directory,
2994 char *directory_name)
2997 static unsigned long progress_delay = 0;
2998 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3000 char *directory_path = getPath2(level_directory, directory_name);
3001 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3002 SetupFileHash *setup_file_hash;
3003 LevelDirTree *leveldir_new = NULL;
3006 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3007 if (!options.debug && !fileExists(filename))
3009 free(directory_path);
3015 setup_file_hash = loadSetupFileHash(filename);
3017 if (setup_file_hash == NULL)
3019 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3021 free(directory_path);
3027 leveldir_new = newTreeInfo();
3030 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3032 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3034 leveldir_new->subdir = getStringCopy(directory_name);
3036 checkSetupFileHashIdentifier(setup_file_hash, filename,
3037 getCookie("LEVELINFO"));
3039 /* set all structure fields according to the token/value pairs */
3040 ldi = *leveldir_new;
3041 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3042 setSetupInfo(levelinfo_tokens, i,
3043 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3044 *leveldir_new = ldi;
3046 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3047 setString(&leveldir_new->name, leveldir_new->subdir);
3049 if (leveldir_new->identifier == NULL)
3050 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3052 if (leveldir_new->name_sorting == NULL)
3053 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3055 if (node_parent == NULL) /* top level group */
3057 leveldir_new->basepath = getStringCopy(level_directory);
3058 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3060 else /* sub level group */
3062 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3063 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3067 if (leveldir_new->levels < 1)
3068 leveldir_new->levels = 1;
3071 leveldir_new->last_level =
3072 leveldir_new->first_level + leveldir_new->levels - 1;
3074 leveldir_new->in_user_dir =
3075 (!strEqual(leveldir_new->basepath, options.level_directory));
3077 /* adjust some settings if user's private level directory was detected */
3078 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3079 leveldir_new->in_user_dir &&
3080 (strEqual(leveldir_new->subdir, getLoginName()) ||
3081 strEqual(leveldir_new->name, getLoginName()) ||
3082 strEqual(leveldir_new->author, getRealName())))
3084 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3085 leveldir_new->readonly = FALSE;
3088 leveldir_new->user_defined =
3089 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3091 leveldir_new->color = LEVELCOLOR(leveldir_new);
3093 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3095 leveldir_new->handicap_level = /* set handicap to default value */
3096 (leveldir_new->user_defined || !leveldir_new->handicap ?
3097 leveldir_new->last_level : leveldir_new->first_level);
3101 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3102 leveldir_new->level_group);
3104 if (leveldir_new->level_group ||
3105 DelayReached(&progress_delay, progress_delay_value))
3106 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3109 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3113 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3115 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3117 /* skip level sets without levels (which are probably artwork base sets) */
3119 freeSetupFileHash(setup_file_hash);
3120 free(directory_path);
3128 pushTreeInfo(node_first, leveldir_new);
3130 freeSetupFileHash(setup_file_hash);
3132 if (leveldir_new->level_group)
3134 /* create node to link back to current level directory */
3135 createParentTreeInfoNode(leveldir_new);
3137 /* recursively step into sub-directory and look for more level series */
3138 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3139 leveldir_new, directory_path);
3142 free(directory_path);
3148 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3149 TreeInfo *node_parent,
3150 char *level_directory)
3153 struct dirent *dir_entry;
3154 boolean valid_entry_found = FALSE;
3156 if ((dir = opendir(level_directory)) == NULL)
3158 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3162 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3164 struct stat file_status;
3165 char *directory_name = dir_entry->d_name;
3166 char *directory_path = getPath2(level_directory, directory_name);
3168 /* skip entries for current and parent directory */
3169 if (strEqual(directory_name, ".") ||
3170 strEqual(directory_name, ".."))
3172 free(directory_path);
3176 /* find out if directory entry is itself a directory */
3177 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3178 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3180 free(directory_path);
3184 free(directory_path);
3186 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3187 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3188 strEqual(directory_name, MUSIC_DIRECTORY))
3191 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3198 /* special case: top level directory may directly contain "levelinfo.conf" */
3199 if (node_parent == NULL && !valid_entry_found)
3201 /* check if this directory directly contains a file "levelinfo.conf" */
3202 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3203 level_directory, ".");
3206 if (!valid_entry_found)
3207 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3211 boolean AdjustGraphicsForEMC()
3213 boolean settings_changed = FALSE;
3215 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3216 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3218 return settings_changed;
3221 void LoadLevelInfo()
3223 InitUserLevelDirectory(getLoginName());
3225 DrawInitText("Loading level series", 120, FC_GREEN);
3227 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3228 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3230 /* after loading all level set information, clone the level directory tree
3231 and remove all level sets without levels (these may still contain artwork
3232 to be offered in the setup menu as "custom artwork", and are therefore
3233 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3234 leveldir_first_all = leveldir_first;
3235 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3237 AdjustGraphicsForEMC();
3239 /* before sorting, the first entries will be from the user directory */
3240 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3242 if (leveldir_first == NULL)
3243 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3245 sortTreeInfo(&leveldir_first);
3248 dumpTreeInfo(leveldir_first, 0);
3252 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3253 TreeInfo *node_parent,
3254 char *base_directory,
3255 char *directory_name, int type)
3257 char *directory_path = getPath2(base_directory, directory_name);
3258 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3259 SetupFileHash *setup_file_hash = NULL;
3260 TreeInfo *artwork_new = NULL;
3263 if (fileExists(filename))
3264 setup_file_hash = loadSetupFileHash(filename);
3266 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3269 struct dirent *dir_entry;
3270 boolean valid_file_found = FALSE;
3272 if ((dir = opendir(directory_path)) != NULL)
3274 while ((dir_entry = readdir(dir)) != NULL)
3276 char *entry_name = dir_entry->d_name;
3278 if (FileIsArtworkType(entry_name, type))
3280 valid_file_found = TRUE;
3288 if (!valid_file_found)
3290 if (!strEqual(directory_name, "."))
3291 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3293 free(directory_path);
3300 artwork_new = newTreeInfo();
3303 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3305 setTreeInfoToDefaults(artwork_new, type);
3307 artwork_new->subdir = getStringCopy(directory_name);
3309 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3312 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3315 /* set all structure fields according to the token/value pairs */
3317 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3318 setSetupInfo(levelinfo_tokens, i,
3319 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3322 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3323 setString(&artwork_new->name, artwork_new->subdir);
3325 if (artwork_new->identifier == NULL)
3326 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3328 if (artwork_new->name_sorting == NULL)
3329 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3332 if (node_parent == NULL) /* top level group */
3334 artwork_new->basepath = getStringCopy(base_directory);
3335 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3337 else /* sub level group */
3339 artwork_new->basepath = getStringCopy(node_parent->basepath);
3340 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3343 artwork_new->in_user_dir =
3344 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3346 /* (may use ".sort_priority" from "setup_file_hash" above) */
3347 artwork_new->color = ARTWORKCOLOR(artwork_new);
3349 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3351 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3353 if (strEqual(artwork_new->subdir, "."))
3355 if (artwork_new->user_defined)
3357 setString(&artwork_new->identifier, "private");
3358 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3362 setString(&artwork_new->identifier, "classic");
3363 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3366 /* set to new values after changing ".sort_priority" */
3367 artwork_new->color = ARTWORKCOLOR(artwork_new);
3369 setString(&artwork_new->class_desc,
3370 getLevelClassDescription(artwork_new));
3374 setString(&artwork_new->identifier, artwork_new->subdir);
3377 setString(&artwork_new->name, artwork_new->identifier);
3378 setString(&artwork_new->name_sorting, artwork_new->name);
3382 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3385 pushTreeInfo(node_first, artwork_new);
3387 freeSetupFileHash(setup_file_hash);
3389 free(directory_path);
3395 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3396 TreeInfo *node_parent,
3397 char *base_directory, int type)
3400 struct dirent *dir_entry;
3401 boolean valid_entry_found = FALSE;
3403 if ((dir = opendir(base_directory)) == NULL)
3405 /* display error if directory is main "options.graphics_directory" etc. */
3406 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3407 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3412 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3414 struct stat file_status;
3415 char *directory_name = dir_entry->d_name;
3416 char *directory_path = getPath2(base_directory, directory_name);
3418 /* skip directory entries for current and parent directory */
3419 if (strEqual(directory_name, ".") ||
3420 strEqual(directory_name, ".."))
3422 free(directory_path);
3426 /* skip directory entries which are not a directory or are not accessible */
3427 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3428 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3430 free(directory_path);
3434 free(directory_path);
3436 /* check if this directory contains artwork with or without config file */
3437 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3439 directory_name, type);
3444 /* check if this directory directly contains artwork itself */
3445 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3446 base_directory, ".",
3448 if (!valid_entry_found)
3449 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3453 static TreeInfo *getDummyArtworkInfo(int type)
3455 /* this is only needed when there is completely no artwork available */
3456 TreeInfo *artwork_new = newTreeInfo();
3458 setTreeInfoToDefaults(artwork_new, type);
3460 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3461 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3462 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3464 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3465 setString(&artwork_new->name, UNDEFINED_FILENAME);
3466 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3471 void LoadArtworkInfo()
3473 LoadArtworkInfoCache();
3475 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3477 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3478 options.graphics_directory,
3479 TREE_TYPE_GRAPHICS_DIR);
3480 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3481 getUserGraphicsDir(),
3482 TREE_TYPE_GRAPHICS_DIR);
3484 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3485 options.sounds_directory,
3486 TREE_TYPE_SOUNDS_DIR);
3487 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3489 TREE_TYPE_SOUNDS_DIR);
3491 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3492 options.music_directory,
3493 TREE_TYPE_MUSIC_DIR);
3494 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3496 TREE_TYPE_MUSIC_DIR);
3498 if (artwork.gfx_first == NULL)
3499 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3500 if (artwork.snd_first == NULL)
3501 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3502 if (artwork.mus_first == NULL)
3503 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3505 /* before sorting, the first entries will be from the user directory */
3506 artwork.gfx_current =
3507 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3508 if (artwork.gfx_current == NULL)
3509 artwork.gfx_current =
3510 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3511 if (artwork.gfx_current == NULL)
3512 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3514 artwork.snd_current =
3515 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3516 if (artwork.snd_current == NULL)
3517 artwork.snd_current =
3518 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3519 if (artwork.snd_current == NULL)
3520 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3522 artwork.mus_current =
3523 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3524 if (artwork.mus_current == NULL)
3525 artwork.mus_current =
3526 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3527 if (artwork.mus_current == NULL)
3528 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3530 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3531 artwork.snd_current_identifier = artwork.snd_current->identifier;
3532 artwork.mus_current_identifier = artwork.mus_current->identifier;
3535 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3536 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3537 printf("music set == %s\n\n", artwork.mus_current_identifier);
3540 sortTreeInfo(&artwork.gfx_first);
3541 sortTreeInfo(&artwork.snd_first);
3542 sortTreeInfo(&artwork.mus_first);
3545 dumpTreeInfo(artwork.gfx_first, 0);
3546 dumpTreeInfo(artwork.snd_first, 0);
3547 dumpTreeInfo(artwork.mus_first, 0);
3551 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3552 LevelDirTree *level_node)
3555 static unsigned long progress_delay = 0;
3556 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3558 int type = (*artwork_node)->type;
3560 /* recursively check all level directories for artwork sub-directories */
3564 /* check all tree entries for artwork, but skip parent link entries */
3565 if (!level_node->parent_link)
3567 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3568 boolean cached = (artwork_new != NULL);
3572 pushTreeInfo(artwork_node, artwork_new);
3576 TreeInfo *topnode_last = *artwork_node;
3577 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3578 ARTWORK_DIRECTORY(type));
3580 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3582 if (topnode_last != *artwork_node) /* check for newly added node */
3584 artwork_new = *artwork_node;
3586 setString(&artwork_new->identifier, level_node->subdir);
3587 setString(&artwork_new->name, level_node->name);
3588 setString(&artwork_new->name_sorting, level_node->name_sorting);
3590 artwork_new->sort_priority = level_node->sort_priority;
3591 artwork_new->color = LEVELCOLOR(artwork_new);
3597 /* insert artwork info (from old cache or filesystem) into new cache */
3598 if (artwork_new != NULL)
3599 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3603 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3604 level_node->level_group);
3606 if (level_node->level_group ||
3607 DelayReached(&progress_delay, progress_delay_value))
3608 DrawInitText(level_node->name, 150, FC_YELLOW);
3611 if (level_node->node_group != NULL)
3612 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3614 level_node = level_node->next;
3618 void LoadLevelArtworkInfo()
3620 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3622 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3623 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3624 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3626 SaveArtworkInfoCache();
3628 /* needed for reloading level artwork not known at ealier stage */
3630 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3632 artwork.gfx_current =
3633 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3634 if (artwork.gfx_current == NULL)
3635 artwork.gfx_current =
3636 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3637 if (artwork.gfx_current == NULL)
3638 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3641 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3643 artwork.snd_current =
3644 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3645 if (artwork.snd_current == NULL)
3646 artwork.snd_current =
3647 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3648 if (artwork.snd_current == NULL)
3649 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3652 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3654 artwork.mus_current =
3655 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3656 if (artwork.mus_current == NULL)
3657 artwork.mus_current =
3658 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3659 if (artwork.mus_current == NULL)
3660 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3663 sortTreeInfo(&artwork.gfx_first);
3664 sortTreeInfo(&artwork.snd_first);
3665 sortTreeInfo(&artwork.mus_first);
3668 dumpTreeInfo(artwork.gfx_first, 0);
3669 dumpTreeInfo(artwork.snd_first, 0);
3670 dumpTreeInfo(artwork.mus_first, 0);
3674 static void SaveUserLevelInfo()
3676 LevelDirTree *level_info;
3681 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3683 if (!(file = fopen(filename, MODE_WRITE)))
3685 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3690 level_info = newTreeInfo();
3692 /* always start with reliable default values */
3693 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3695 setString(&level_info->name, getLoginName());
3696 setString(&level_info->author, getRealName());
3697 level_info->levels = 100;
3698 level_info->first_level = 1;
3700 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3702 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3703 getCookie("LEVELINFO")));
3706 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3708 if (i == LEVELINFO_TOKEN_NAME ||
3709 i == LEVELINFO_TOKEN_AUTHOR ||
3710 i == LEVELINFO_TOKEN_LEVELS ||
3711 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3712 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3714 /* just to make things nicer :) */
3715 if (i == LEVELINFO_TOKEN_AUTHOR)
3716 fprintf(file, "\n");
3719 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3723 SetFilePermissions(filename, PERMS_PRIVATE);
3725 freeTreeInfo(level_info);
3729 char *getSetupValue(int type, void *value)
3731 static char value_string[MAX_LINE_LEN];
3739 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3743 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3747 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3751 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3755 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3759 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3763 sprintf(value_string, "%d", *(int *)value);
3767 if (*(char **)value == NULL)
3770 strcpy(value_string, *(char **)value);
3774 value_string[0] = '\0';
3778 if (type & TYPE_GHOSTED)
3779 strcpy(value_string, "n/a");
3781 return value_string;
3784 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3788 static char token_string[MAX_LINE_LEN];
3789 int token_type = token_info[token_nr].type;
3790 void *setup_value = token_info[token_nr].value;
3791 char *token_text = token_info[token_nr].text;
3792 char *value_string = getSetupValue(token_type, setup_value);
3794 /* build complete token string */
3795 sprintf(token_string, "%s%s", prefix, token_text);
3797 /* build setup entry line */
3798 line = getFormattedSetupEntry(token_string, value_string);
3800 if (token_type == TYPE_KEY_X11)
3802 Key key = *(Key *)setup_value;
3803 char *keyname = getKeyNameFromKey(key);
3805 /* add comment, if useful */
3806 if (!strEqual(keyname, "(undefined)") &&
3807 !strEqual(keyname, "(unknown)"))
3809 /* add at least one whitespace */
3811 for (i = strlen(line); i < token_comment_position; i++)
3815 strcat(line, keyname);
3822 void LoadLevelSetup_LastSeries()
3824 /* ----------------------------------------------------------------------- */
3825 /* ~/.<program>/levelsetup.conf */
3826 /* ----------------------------------------------------------------------- */
3828 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3829 SetupFileHash *level_setup_hash = NULL;
3831 /* always start with reliable default values */
3832 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3834 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3835 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3837 if (leveldir_current == NULL)
3838 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3841 if ((level_setup_hash = loadSetupFileHash(filename)))
3843 char *last_level_series =
3844 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3846 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3848 if (leveldir_current == NULL)
3849 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3851 checkSetupFileHashIdentifier(level_setup_hash, filename,
3852 getCookie("LEVELSETUP"));
3854 freeSetupFileHash(level_setup_hash);
3857 Error(ERR_WARN, "using default setup values");
3862 void SaveLevelSetup_LastSeries()
3864 /* ----------------------------------------------------------------------- */
3865 /* ~/.<program>/levelsetup.conf */
3866 /* ----------------------------------------------------------------------- */
3868 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3869 char *level_subdir = leveldir_current->subdir;
3872 InitUserDataDirectory();
3874 if (!(file = fopen(filename, MODE_WRITE)))
3876 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3881 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3882 getCookie("LEVELSETUP")));
3883 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3888 SetFilePermissions(filename, PERMS_PRIVATE);
3893 static void checkSeriesInfo()
3895 static char *level_directory = NULL;
3897 struct dirent *dir_entry;
3899 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3901 level_directory = getPath2((leveldir_current->in_user_dir ?
3902 getUserLevelDir(NULL) :
3903 options.level_directory),
3904 leveldir_current->fullpath);
3906 if ((dir = opendir(level_directory)) == NULL)
3908 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3912 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3914 if (strlen(dir_entry->d_name) > 4 &&
3915 dir_entry->d_name[3] == '.' &&
3916 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3918 char levelnum_str[4];
3921 strncpy(levelnum_str, dir_entry->d_name, 3);
3922 levelnum_str[3] = '\0';
3924 levelnum_value = atoi(levelnum_str);
3927 if (levelnum_value < leveldir_current->first_level)
3929 Error(ERR_WARN, "additional level %d found", levelnum_value);
3930 leveldir_current->first_level = levelnum_value;
3932 else if (levelnum_value > leveldir_current->last_level)
3934 Error(ERR_WARN, "additional level %d found", levelnum_value);
3935 leveldir_current->last_level = levelnum_value;
3944 void LoadLevelSetup_SeriesInfo()
3947 SetupFileHash *level_setup_hash = NULL;
3948 char *level_subdir = leveldir_current->subdir;
3950 /* always start with reliable default values */
3951 level_nr = leveldir_current->first_level;
3953 checkSeriesInfo(leveldir_current);
3955 /* ----------------------------------------------------------------------- */
3956 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3957 /* ----------------------------------------------------------------------- */
3959 level_subdir = leveldir_current->subdir;
3961 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3963 if ((level_setup_hash = loadSetupFileHash(filename)))
3967 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3971 level_nr = atoi(token_value);
3973 if (level_nr < leveldir_current->first_level)
3974 level_nr = leveldir_current->first_level;
3975 if (level_nr > leveldir_current->last_level)
3976 level_nr = leveldir_current->last_level;
3979 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3983 int level_nr = atoi(token_value);
3985 if (level_nr < leveldir_current->first_level)
3986 level_nr = leveldir_current->first_level;
3987 if (level_nr > leveldir_current->last_level + 1)
3988 level_nr = leveldir_current->last_level;
3990 if (leveldir_current->user_defined || !leveldir_current->handicap)
3991 level_nr = leveldir_current->last_level;
3993 leveldir_current->handicap_level = level_nr;
3996 checkSetupFileHashIdentifier(level_setup_hash, filename,
3997 getCookie("LEVELSETUP"));
3999 freeSetupFileHash(level_setup_hash);
4002 Error(ERR_WARN, "using default setup values");
4007 void SaveLevelSetup_SeriesInfo()
4010 char *level_subdir = leveldir_current->subdir;
4011 char *level_nr_str = int2str(level_nr, 0);
4012 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4015 /* ----------------------------------------------------------------------- */
4016 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4017 /* ----------------------------------------------------------------------- */
4019 InitLevelSetupDirectory(level_subdir);
4021 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4023 if (!(file = fopen(filename, MODE_WRITE)))
4025 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4030 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4031 getCookie("LEVELSETUP")));
4032 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4034 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4035 handicap_level_str));
4039 SetFilePermissions(filename, PERMS_PRIVATE);