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)))
338 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
343 No (or non-existing) artwork configured in "levelinfo.conf". This would
344 normally result in using the artwork configured in the setup menu. But
345 if an artwork subdirectory exists (which might contain custom artwork
346 or an artwork configuration file), this level artwork must be treated
347 as relative to the default "classic" artwork, not to the artwork that
348 is currently configured in the setup menu.
350 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
351 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
352 the real "classic" artwork from the original R'n'D (like "gfx_classic").
355 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
357 checked_free(*artwork_set_ptr);
361 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
362 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
366 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
367 *artwork_set_ptr = NULL;
373 return *artwork_set_ptr;
376 inline static char *getLevelArtworkSet(int type)
378 if (leveldir_current == NULL)
381 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
384 inline static char *getLevelArtworkDir(int type)
386 if (leveldir_current == NULL)
387 return UNDEFINED_FILENAME;
389 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
392 char *getTapeFilename(int nr)
394 static char *filename = NULL;
395 char basename[MAX_FILENAME_LEN];
397 checked_free(filename);
399 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
400 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
405 char *getSolutionTapeFilename(int nr)
407 static char *filename = NULL;
408 char basename[MAX_FILENAME_LEN];
410 checked_free(filename);
412 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
413 filename = getPath2(getSolutionTapeDir(), basename);
418 char *getScoreFilename(int nr)
420 static char *filename = NULL;
421 char basename[MAX_FILENAME_LEN];
423 checked_free(filename);
425 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
426 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
431 char *getSetupFilename()
433 static char *filename = NULL;
435 checked_free(filename);
437 filename = getPath2(getSetupDir(), SETUP_FILENAME);
442 char *getEditorSetupFilename()
444 static char *filename = NULL;
446 checked_free(filename);
447 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
449 if (fileExists(filename))
452 checked_free(filename);
453 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
458 char *getHelpAnimFilename()
460 static char *filename = NULL;
462 checked_free(filename);
464 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
469 char *getHelpTextFilename()
471 static char *filename = NULL;
473 checked_free(filename);
475 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
480 char *getLevelSetInfoFilename()
482 static char *filename = NULL;
497 for (i = 0; basenames[i] != NULL; i++)
499 checked_free(filename);
500 filename = getPath2(getCurrentLevelDir(), basenames[i]);
502 if (fileExists(filename))
509 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
511 static char basename[32];
513 sprintf(basename, "%s_%d.txt",
514 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
519 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
521 static char *filename = NULL;
523 boolean skip_setup_artwork = FALSE;
525 checked_free(filename);
527 basename = getLevelSetTitleMessageBasename(nr, initial);
529 if (!gfx.override_level_graphics)
531 /* 1st try: look for special artwork in current level series directory */
532 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
533 if (fileExists(filename))
538 /* 2nd try: look for message file in current level set directory */
539 filename = getPath2(getCurrentLevelDir(), basename);
540 if (fileExists(filename))
545 /* check if there is special artwork configured in level series config */
546 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
548 /* 3rd try: look for special artwork configured in level series config */
549 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
550 if (fileExists(filename))
555 /* take missing artwork configured in level set config from default */
556 skip_setup_artwork = TRUE;
560 if (!skip_setup_artwork)
562 /* 4th try: look for special artwork in configured artwork directory */
563 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
564 if (fileExists(filename))
570 /* 5th try: look for default artwork in new default artwork directory */
571 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
572 if (fileExists(filename))
577 /* 6th try: look for default artwork in old default artwork directory */
578 filename = getPath2(options.graphics_directory, basename);
579 if (fileExists(filename))
582 return NULL; /* cannot find specified artwork file anywhere */
585 static char *getCorrectedArtworkBasename(char *basename)
587 char *basename_corrected = basename;
589 #if defined(PLATFORM_MSDOS)
590 if (program.filename_prefix != NULL)
592 int prefix_len = strlen(program.filename_prefix);
594 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
595 basename_corrected = &basename[prefix_len];
597 /* if corrected filename is still longer than standard MS-DOS filename
598 size (8 characters + 1 dot + 3 characters file extension), shorten
599 filename by writing file extension after 8th basename character */
600 if (strlen(basename_corrected) > 8 + 1 + 3)
602 static char *msdos_filename = NULL;
604 checked_free(msdos_filename);
606 msdos_filename = getStringCopy(basename_corrected);
607 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
609 basename_corrected = msdos_filename;
614 return basename_corrected;
617 char *getCustomImageFilename(char *basename)
619 static char *filename = NULL;
620 boolean skip_setup_artwork = FALSE;
622 checked_free(filename);
624 basename = getCorrectedArtworkBasename(basename);
626 if (!gfx.override_level_graphics)
628 /* 1st try: look for special artwork in current level series directory */
629 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
630 if (fileExists(filename))
635 /* check if there is special artwork configured in level series config */
636 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
638 /* 2nd try: look for special artwork configured in level series config */
639 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
640 if (fileExists(filename))
645 /* take missing artwork configured in level set config from default */
646 skip_setup_artwork = TRUE;
650 if (!skip_setup_artwork)
652 /* 3rd try: look for special artwork in configured artwork directory */
653 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
654 if (fileExists(filename))
660 /* 4th try: look for default artwork in new default artwork directory */
661 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
662 if (fileExists(filename))
667 /* 5th try: look for default artwork in old default artwork directory */
668 filename = getPath2(options.graphics_directory, basename);
669 if (fileExists(filename))
672 #if defined(CREATE_SPECIAL_EDITION)
676 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", 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)
747 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
749 /* 6th try: look for fallback artwork in old default artwork directory */
750 /* (needed to prevent errors when trying to access unused artwork files) */
751 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
752 if (fileExists(filename))
756 return NULL; /* cannot find specified artwork file anywhere */
759 char *getCustomMusicFilename(char *basename)
761 static char *filename = NULL;
762 boolean skip_setup_artwork = FALSE;
764 checked_free(filename);
766 basename = getCorrectedArtworkBasename(basename);
768 if (!gfx.override_level_music)
770 /* 1st try: look for special artwork in current level series directory */
771 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
772 if (fileExists(filename))
777 /* check if there is special artwork configured in level series config */
778 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
780 /* 2nd try: look for special artwork configured in level series config */
781 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
782 if (fileExists(filename))
787 /* take missing artwork configured in level set config from default */
788 skip_setup_artwork = TRUE;
792 if (!skip_setup_artwork)
794 /* 3rd try: look for special artwork in configured artwork directory */
795 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
796 if (fileExists(filename))
802 /* 4th try: look for default artwork in new default artwork directory */
803 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
804 if (fileExists(filename))
809 /* 5th try: look for default artwork in old default artwork directory */
810 filename = getPath2(options.music_directory, basename);
811 if (fileExists(filename))
814 #if defined(CREATE_SPECIAL_EDITION)
818 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
820 /* 6th try: look for fallback artwork in old default artwork directory */
821 /* (needed to prevent errors when trying to access unused artwork files) */
822 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
823 if (fileExists(filename))
827 return NULL; /* cannot find specified artwork file anywhere */
830 char *getCustomArtworkFilename(char *basename, int type)
832 if (type == ARTWORK_TYPE_GRAPHICS)
833 return getCustomImageFilename(basename);
834 else if (type == ARTWORK_TYPE_SOUNDS)
835 return getCustomSoundFilename(basename);
836 else if (type == ARTWORK_TYPE_MUSIC)
837 return getCustomMusicFilename(basename);
839 return UNDEFINED_FILENAME;
842 char *getCustomArtworkConfigFilename(int type)
844 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
847 char *getCustomArtworkLevelConfigFilename(int type)
849 static char *filename = NULL;
851 checked_free(filename);
853 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
858 char *getCustomMusicDirectory(void)
860 static char *directory = NULL;
861 boolean skip_setup_artwork = FALSE;
863 checked_free(directory);
865 if (!gfx.override_level_music)
867 /* 1st try: look for special artwork in current level series directory */
868 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
869 if (fileExists(directory))
874 /* check if there is special artwork configured in level series config */
875 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
877 /* 2nd try: look for special artwork configured in level series config */
878 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
879 if (fileExists(directory))
884 /* take missing artwork configured in level set config from default */
885 skip_setup_artwork = TRUE;
889 if (!skip_setup_artwork)
891 /* 3rd try: look for special artwork in configured artwork directory */
892 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
893 if (fileExists(directory))
899 /* 4th try: look for default artwork in new default artwork directory */
900 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
901 if (fileExists(directory))
906 /* 5th try: look for default artwork in old default artwork directory */
907 directory = getStringCopy(options.music_directory);
908 if (fileExists(directory))
911 return NULL; /* cannot find specified artwork file anywhere */
914 void InitTapeDirectory(char *level_subdir)
916 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
917 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
918 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
921 void InitScoreDirectory(char *level_subdir)
923 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
924 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
925 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
928 static void SaveUserLevelInfo();
930 void InitUserLevelDirectory(char *level_subdir)
932 if (!fileExists(getUserLevelDir(level_subdir)))
934 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
935 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
936 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
942 void InitLevelSetupDirectory(char *level_subdir)
944 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
945 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
946 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
949 void InitCacheDirectory()
951 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
952 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
956 /* ------------------------------------------------------------------------- */
957 /* some functions to handle lists of level and artwork directories */
958 /* ------------------------------------------------------------------------- */
960 TreeInfo *newTreeInfo()
962 return checked_calloc(sizeof(TreeInfo));
965 TreeInfo *newTreeInfo_setDefaults(int type)
967 TreeInfo *ti = newTreeInfo();
969 setTreeInfoToDefaults(ti, type);
974 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
976 node_new->next = *node_first;
977 *node_first = node_new;
980 int numTreeInfo(TreeInfo *node)
993 boolean validLevelSeries(TreeInfo *node)
995 return (node != NULL && !node->node_group && !node->parent_link);
998 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1003 if (node->node_group) /* enter level group (step down into tree) */
1004 return getFirstValidTreeInfoEntry(node->node_group);
1005 else if (node->parent_link) /* skip start entry of level group */
1007 if (node->next) /* get first real level series entry */
1008 return getFirstValidTreeInfoEntry(node->next);
1009 else /* leave empty level group and go on */
1010 return getFirstValidTreeInfoEntry(node->node_parent->next);
1012 else /* this seems to be a regular level series */
1016 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1021 if (node->node_parent == NULL) /* top level group */
1022 return *node->node_top;
1023 else /* sub level group */
1024 return node->node_parent->node_group;
1027 int numTreeInfoInGroup(TreeInfo *node)
1029 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1032 int posTreeInfo(TreeInfo *node)
1034 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1039 if (node_cmp == node)
1043 node_cmp = node_cmp->next;
1049 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1051 TreeInfo *node_default = node;
1063 return node_default;
1066 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1068 if (identifier == NULL)
1073 if (node->node_group)
1075 TreeInfo *node_group;
1077 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1082 else if (!node->parent_link)
1084 if (strEqual(identifier, node->identifier))
1094 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1095 TreeInfo *node, boolean skip_sets_without_levels)
1102 if (!node->parent_link && !node->level_group &&
1103 skip_sets_without_levels && node->levels == 0)
1104 return cloneTreeNode(node_top, node_parent, node->next,
1105 skip_sets_without_levels);
1108 node_new = getTreeInfoCopy(node); /* copy complete node */
1110 node_new = newTreeInfo();
1112 *node_new = *node; /* copy complete node */
1115 node_new->node_top = node_top; /* correct top node link */
1116 node_new->node_parent = node_parent; /* correct parent node link */
1118 if (node->level_group)
1119 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1120 skip_sets_without_levels);
1122 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1123 skip_sets_without_levels);
1128 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1130 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1132 *ti_new = ti_cloned;
1135 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1137 boolean settings_changed = FALSE;
1141 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1142 !strEqual(node->graphics_set, node->graphics_set_ecs))
1144 setString(&node->graphics_set, node->graphics_set_ecs);
1145 settings_changed = TRUE;
1147 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1148 !strEqual(node->graphics_set, node->graphics_set_aga))
1150 setString(&node->graphics_set, node->graphics_set_aga);
1151 settings_changed = TRUE;
1154 if (node->node_group != NULL)
1155 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1160 return settings_changed;
1163 void dumpTreeInfo(TreeInfo *node, int depth)
1167 printf("Dumping TreeInfo:\n");
1171 for (i = 0; i < (depth + 1) * 3; i++)
1174 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1175 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1177 if (node->node_group != NULL)
1178 dumpTreeInfo(node->node_group, depth + 1);
1184 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1185 int (*compare_function)(const void *,
1188 int num_nodes = numTreeInfo(*node_first);
1189 TreeInfo **sort_array;
1190 TreeInfo *node = *node_first;
1196 /* allocate array for sorting structure pointers */
1197 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1199 /* writing structure pointers to sorting array */
1200 while (i < num_nodes && node) /* double boundary check... */
1202 sort_array[i] = node;
1208 /* sorting the structure pointers in the sorting array */
1209 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1212 /* update the linkage of list elements with the sorted node array */
1213 for (i = 0; i < num_nodes - 1; i++)
1214 sort_array[i]->next = sort_array[i + 1];
1215 sort_array[num_nodes - 1]->next = NULL;
1217 /* update the linkage of the main list anchor pointer */
1218 *node_first = sort_array[0];
1222 /* now recursively sort the level group structures */
1226 if (node->node_group != NULL)
1227 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1233 void sortTreeInfo(TreeInfo **node_first)
1235 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1239 /* ========================================================================= */
1240 /* some stuff from "files.c" */
1241 /* ========================================================================= */
1243 #if defined(PLATFORM_WIN32)
1245 #define S_IRGRP S_IRUSR
1248 #define S_IROTH S_IRUSR
1251 #define S_IWGRP S_IWUSR
1254 #define S_IWOTH S_IWUSR
1257 #define S_IXGRP S_IXUSR
1260 #define S_IXOTH S_IXUSR
1263 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1268 #endif /* PLATFORM_WIN32 */
1270 /* file permissions for newly written files */
1271 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1272 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1273 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1275 #define MODE_W_PRIVATE (S_IWUSR)
1276 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1277 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1279 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1280 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1282 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1283 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1287 static char *dir = NULL;
1289 #if defined(PLATFORM_WIN32)
1292 dir = checked_malloc(MAX_PATH + 1);
1294 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1297 #elif defined(PLATFORM_UNIX)
1300 if ((dir = getenv("HOME")) == NULL)
1304 if ((pwd = getpwuid(getuid())) != NULL)
1305 dir = getStringCopy(pwd->pw_dir);
1317 char *getCommonDataDir(void)
1319 static char *common_data_dir = NULL;
1321 #if defined(PLATFORM_WIN32)
1322 if (common_data_dir == NULL)
1324 char *dir = checked_malloc(MAX_PATH + 1);
1326 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1327 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1328 common_data_dir = getPath2(dir, program.userdata_subdir);
1330 common_data_dir = options.rw_base_directory;
1333 if (common_data_dir == NULL)
1334 common_data_dir = options.rw_base_directory;
1337 return common_data_dir;
1340 char *getPersonalDataDir(void)
1342 static char *personal_data_dir = NULL;
1344 #if defined(PLATFORM_MACOSX)
1345 if (personal_data_dir == NULL)
1346 personal_data_dir = getPath2(getHomeDir(), "Documents");
1348 if (personal_data_dir == NULL)
1349 personal_data_dir = getHomeDir();
1352 return personal_data_dir;
1355 char *getUserGameDataDir(void)
1357 static char *user_game_data_dir = NULL;
1359 if (user_game_data_dir == NULL)
1360 user_game_data_dir = getPath2(getPersonalDataDir(),
1361 program.userdata_subdir);
1363 return user_game_data_dir;
1366 void updateUserGameDataDir()
1368 #if defined(PLATFORM_MACOSX)
1369 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1370 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1372 /* convert old Unix style game data directory to Mac OS X style, if needed */
1373 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1375 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1377 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1378 userdata_dir_old, userdata_dir_new);
1380 /* continue using Unix style data directory -- this should not happen */
1381 program.userdata_path = getPath2(getPersonalDataDir(),
1382 program.userdata_subdir_unix);
1386 free(userdata_dir_old);
1392 return getUserGameDataDir();
1395 static mode_t posix_umask(mode_t mask)
1397 #if defined(PLATFORM_UNIX)
1404 static int posix_mkdir(const char *pathname, mode_t mode)
1406 #if defined(PLATFORM_WIN32)
1407 return mkdir(pathname);
1409 return mkdir(pathname, mode);
1413 void createDirectory(char *dir, char *text, int permission_class)
1415 /* leave "other" permissions in umask untouched, but ensure group parts
1416 of USERDATA_DIR_MODE are not masked */
1417 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1418 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1419 mode_t normal_umask = posix_umask(0);
1420 mode_t group_umask = ~(dir_mode & S_IRWXG);
1421 posix_umask(normal_umask & group_umask);
1423 if (!fileExists(dir))
1424 if (posix_mkdir(dir, dir_mode) != 0)
1425 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1427 posix_umask(normal_umask); /* reset normal umask */
1430 void InitUserDataDirectory()
1432 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1435 void SetFilePermissions(char *filename, int permission_class)
1437 chmod(filename, (permission_class == PERMS_PRIVATE ?
1438 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1441 char *getCookie(char *file_type)
1443 static char cookie[MAX_COOKIE_LEN + 1];
1445 if (strlen(program.cookie_prefix) + 1 +
1446 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1447 return "[COOKIE ERROR]"; /* should never happen */
1449 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1450 program.cookie_prefix, file_type,
1451 program.version_major, program.version_minor);
1456 int getFileVersionFromCookieString(const char *cookie)
1458 const char *ptr_cookie1, *ptr_cookie2;
1459 const char *pattern1 = "_FILE_VERSION_";
1460 const char *pattern2 = "?.?";
1461 const int len_cookie = strlen(cookie);
1462 const int len_pattern1 = strlen(pattern1);
1463 const int len_pattern2 = strlen(pattern2);
1464 const int len_pattern = len_pattern1 + len_pattern2;
1465 int version_major, version_minor;
1467 if (len_cookie <= len_pattern)
1470 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1471 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1473 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1476 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1477 ptr_cookie2[1] != '.' ||
1478 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1481 version_major = ptr_cookie2[0] - '0';
1482 version_minor = ptr_cookie2[2] - '0';
1484 return VERSION_IDENT(version_major, version_minor, 0, 0);
1487 boolean checkCookieString(const char *cookie, const char *template)
1489 const char *pattern = "_FILE_VERSION_?.?";
1490 const int len_cookie = strlen(cookie);
1491 const int len_template = strlen(template);
1492 const int len_pattern = strlen(pattern);
1494 if (len_cookie != len_template)
1497 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1503 /* ------------------------------------------------------------------------- */
1504 /* setup file list and hash handling functions */
1505 /* ------------------------------------------------------------------------- */
1507 char *getFormattedSetupEntry(char *token, char *value)
1510 static char entry[MAX_LINE_LEN];
1512 /* if value is an empty string, just return token without value */
1516 /* start with the token and some spaces to format output line */
1517 sprintf(entry, "%s:", token);
1518 for (i = strlen(entry); i < token_value_position; i++)
1521 /* continue with the token's value */
1522 strcat(entry, value);
1527 SetupFileList *newSetupFileList(char *token, char *value)
1529 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1531 new->token = getStringCopy(token);
1532 new->value = getStringCopy(value);
1539 void freeSetupFileList(SetupFileList *list)
1544 checked_free(list->token);
1545 checked_free(list->value);
1548 freeSetupFileList(list->next);
1553 char *getListEntry(SetupFileList *list, char *token)
1558 if (strEqual(list->token, token))
1561 return getListEntry(list->next, token);
1564 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1569 if (strEqual(list->token, token))
1571 checked_free(list->value);
1573 list->value = getStringCopy(value);
1577 else if (list->next == NULL)
1578 return (list->next = newSetupFileList(token, value));
1580 return setListEntry(list->next, token, value);
1583 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1588 if (list->next == NULL)
1589 return (list->next = newSetupFileList(token, value));
1591 return addListEntry(list->next, token, value);
1595 static void printSetupFileList(SetupFileList *list)
1600 printf("token: '%s'\n", list->token);
1601 printf("value: '%s'\n", list->value);
1603 printSetupFileList(list->next);
1608 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1609 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1610 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1611 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1613 #define insert_hash_entry hashtable_insert
1614 #define search_hash_entry hashtable_search
1615 #define change_hash_entry hashtable_change
1616 #define remove_hash_entry hashtable_remove
1619 static unsigned int get_hash_from_key(void *key)
1624 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1625 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1626 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1627 it works better than many other constants, prime or not) has never been
1628 adequately explained.
1630 If you just want to have a good hash function, and cannot wait, djb2
1631 is one of the best string hash functions i know. It has excellent
1632 distribution and speed on many different sets of keys and table sizes.
1633 You are not likely to do better with one of the "well known" functions
1634 such as PJW, K&R, etc.
1636 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1639 char *str = (char *)key;
1640 unsigned int hash = 5381;
1643 while ((c = *str++))
1644 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1649 static int keys_are_equal(void *key1, void *key2)
1651 return (strEqual((char *)key1, (char *)key2));
1654 SetupFileHash *newSetupFileHash()
1656 SetupFileHash *new_hash =
1657 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1659 if (new_hash == NULL)
1660 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1665 void freeSetupFileHash(SetupFileHash *hash)
1670 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1673 char *getHashEntry(SetupFileHash *hash, char *token)
1678 return search_hash_entry(hash, token);
1681 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1688 value_copy = getStringCopy(value);
1690 /* change value; if it does not exist, insert it as new */
1691 if (!change_hash_entry(hash, token, value_copy))
1692 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1693 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1696 char *removeHashEntry(SetupFileHash *hash, char *token)
1701 return remove_hash_entry(hash, token);
1705 static void printSetupFileHash(SetupFileHash *hash)
1707 BEGIN_HASH_ITERATION(hash, itr)
1709 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1710 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1712 END_HASH_ITERATION(hash, itr)
1716 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1717 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1718 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1720 static boolean token_value_separator_found = FALSE;
1721 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1722 static boolean token_value_separator_warning = FALSE;
1724 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1725 static boolean token_already_exists_warning = FALSE;
1728 static boolean getTokenValueFromSetupLineExt(char *line,
1729 char **token_ptr, char **value_ptr,
1730 char *filename, char *line_raw,
1732 boolean separator_required)
1734 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1735 char *token, *value, *line_ptr;
1737 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1738 if (line_raw == NULL)
1740 strncpy(line_copy, line, MAX_LINE_LEN);
1741 line_copy[MAX_LINE_LEN] = '\0';
1744 strcpy(line_raw_copy, line_copy);
1745 line_raw = line_raw_copy;
1748 /* cut trailing comment from input line */
1749 for (line_ptr = line; *line_ptr; line_ptr++)
1751 if (*line_ptr == '#')
1758 /* cut trailing whitespaces from input line */
1759 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1760 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1763 /* ignore empty lines */
1767 /* cut leading whitespaces from token */
1768 for (token = line; *token; token++)
1769 if (*token != ' ' && *token != '\t')
1772 /* start with empty value as reliable default */
1775 token_value_separator_found = FALSE;
1777 /* find end of token to determine start of value */
1778 for (line_ptr = token; *line_ptr; line_ptr++)
1781 /* first look for an explicit token/value separator, like ':' or '=' */
1782 if (*line_ptr == ':' || *line_ptr == '=')
1784 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1787 *line_ptr = '\0'; /* terminate token string */
1788 value = line_ptr + 1; /* set beginning of value */
1790 token_value_separator_found = TRUE;
1796 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1797 /* fallback: if no token/value separator found, also allow whitespaces */
1798 if (!token_value_separator_found && !separator_required)
1800 for (line_ptr = token; *line_ptr; line_ptr++)
1802 if (*line_ptr == ' ' || *line_ptr == '\t')
1804 *line_ptr = '\0'; /* terminate token string */
1805 value = line_ptr + 1; /* set beginning of value */
1807 token_value_separator_found = TRUE;
1813 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1814 if (token_value_separator_found)
1816 if (!token_value_separator_warning)
1818 Error(ERR_INFO_LINE, "-");
1820 if (filename != NULL)
1822 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1823 Error(ERR_INFO, "- config file: '%s'", filename);
1827 Error(ERR_WARN, "missing token/value separator(s):");
1830 token_value_separator_warning = TRUE;
1833 if (filename != NULL)
1834 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1836 Error(ERR_INFO, "- line: '%s'", line_raw);
1842 /* cut trailing whitespaces from token */
1843 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1844 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1847 /* cut leading whitespaces from value */
1848 for (; *value; value++)
1849 if (*value != ' ' && *value != '\t')
1854 value = "true"; /* treat tokens without value as "true" */
1863 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1865 /* while the internal (old) interface does not require a token/value
1866 separator (for downwards compatibility with existing files which
1867 don't use them), it is mandatory for the external (new) interface */
1869 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1873 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1874 boolean top_recursion_level, boolean is_hash)
1876 static SetupFileHash *include_filename_hash = NULL;
1877 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1878 char *token, *value, *line_ptr;
1879 void *insert_ptr = NULL;
1880 boolean read_continued_line = FALSE;
1882 int line_nr = 0, token_count = 0, include_count = 0;
1884 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1885 token_value_separator_warning = FALSE;
1888 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1889 token_already_exists_warning = FALSE;
1892 if (!(file = fopen(filename, MODE_READ)))
1894 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1899 /* use "insert pointer" to store list end for constant insertion complexity */
1901 insert_ptr = setup_file_data;
1903 /* on top invocation, create hash to mark included files (to prevent loops) */
1904 if (top_recursion_level)
1905 include_filename_hash = newSetupFileHash();
1907 /* mark this file as already included (to prevent including it again) */
1908 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1912 /* read next line of input file */
1913 if (!fgets(line, MAX_LINE_LEN, file))
1916 /* check if line was completely read and is terminated by line break */
1917 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1920 /* cut trailing line break (this can be newline and/or carriage return) */
1921 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1922 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1925 /* copy raw input line for later use (mainly debugging output) */
1926 strcpy(line_raw, line);
1928 if (read_continued_line)
1931 /* !!! ??? WHY ??? !!! */
1932 /* cut leading whitespaces from input line */
1933 for (line_ptr = line; *line_ptr; line_ptr++)
1934 if (*line_ptr != ' ' && *line_ptr != '\t')
1938 /* append new line to existing line, if there is enough space */
1939 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1940 strcat(previous_line, line_ptr);
1942 strcpy(line, previous_line); /* copy storage buffer to line */
1944 read_continued_line = FALSE;
1947 /* if the last character is '\', continue at next line */
1948 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1950 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1951 strcpy(previous_line, line); /* copy line to storage buffer */
1953 read_continued_line = TRUE;
1958 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1959 line_raw, line_nr, FALSE))
1964 if (strEqual(token, "include"))
1966 if (getHashEntry(include_filename_hash, value) == NULL)
1968 char *basepath = getBasePath(filename);
1969 char *basename = getBaseName(value);
1970 char *filename_include = getPath2(basepath, basename);
1973 Error(ERR_INFO, "[including file '%s']", filename_include);
1976 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1980 free(filename_include);
1986 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1993 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1995 getHashEntry((SetupFileHash *)setup_file_data, token);
1997 if (old_value != NULL)
1999 if (!token_already_exists_warning)
2001 Error(ERR_INFO_LINE, "-");
2002 Error(ERR_WARN, "duplicate token(s) found in config file:");
2003 Error(ERR_INFO, "- config file: '%s'", filename);
2005 token_already_exists_warning = TRUE;
2008 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2009 Error(ERR_INFO, " old value: '%s'", old_value);
2010 Error(ERR_INFO, " new value: '%s'", value);
2014 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2018 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2028 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2029 if (token_value_separator_warning)
2030 Error(ERR_INFO_LINE, "-");
2033 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2034 if (token_already_exists_warning)
2035 Error(ERR_INFO_LINE, "-");
2038 if (token_count == 0 && include_count == 0)
2039 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2041 if (top_recursion_level)
2042 freeSetupFileHash(include_filename_hash);
2049 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2050 boolean top_recursion_level, boolean is_hash)
2052 static SetupFileHash *include_filename_hash = NULL;
2053 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2054 char *token, *value, *line_ptr;
2055 void *insert_ptr = NULL;
2056 boolean read_continued_line = FALSE;
2059 int token_count = 0;
2061 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2062 token_value_separator_warning = FALSE;
2065 if (!(file = fopen(filename, MODE_READ)))
2067 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2072 /* use "insert pointer" to store list end for constant insertion complexity */
2074 insert_ptr = setup_file_data;
2076 /* on top invocation, create hash to mark included files (to prevent loops) */
2077 if (top_recursion_level)
2078 include_filename_hash = newSetupFileHash();
2080 /* mark this file as already included (to prevent including it again) */
2081 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2085 /* read next line of input file */
2086 if (!fgets(line, MAX_LINE_LEN, file))
2089 /* check if line was completely read and is terminated by line break */
2090 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2093 /* cut trailing line break (this can be newline and/or carriage return) */
2094 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2095 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2098 /* copy raw input line for later use (mainly debugging output) */
2099 strcpy(line_raw, line);
2101 if (read_continued_line)
2103 /* cut leading whitespaces from input line */
2104 for (line_ptr = line; *line_ptr; line_ptr++)
2105 if (*line_ptr != ' ' && *line_ptr != '\t')
2108 /* append new line to existing line, if there is enough space */
2109 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2110 strcat(previous_line, line_ptr);
2112 strcpy(line, previous_line); /* copy storage buffer to line */
2114 read_continued_line = FALSE;
2117 /* if the last character is '\', continue at next line */
2118 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2120 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2121 strcpy(previous_line, line); /* copy line to storage buffer */
2123 read_continued_line = TRUE;
2128 /* cut trailing comment from input line */
2129 for (line_ptr = line; *line_ptr; line_ptr++)
2131 if (*line_ptr == '#')
2138 /* cut trailing whitespaces from input line */
2139 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2140 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2143 /* ignore empty lines */
2147 /* cut leading whitespaces from token */
2148 for (token = line; *token; token++)
2149 if (*token != ' ' && *token != '\t')
2152 /* start with empty value as reliable default */
2155 token_value_separator_found = FALSE;
2157 /* find end of token to determine start of value */
2158 for (line_ptr = token; *line_ptr; line_ptr++)
2161 /* first look for an explicit token/value separator, like ':' or '=' */
2162 if (*line_ptr == ':' || *line_ptr == '=')
2164 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2167 *line_ptr = '\0'; /* terminate token string */
2168 value = line_ptr + 1; /* set beginning of value */
2170 token_value_separator_found = TRUE;
2176 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2177 /* fallback: if no token/value separator found, also allow whitespaces */
2178 if (!token_value_separator_found)
2180 for (line_ptr = token; *line_ptr; line_ptr++)
2182 if (*line_ptr == ' ' || *line_ptr == '\t')
2184 *line_ptr = '\0'; /* terminate token string */
2185 value = line_ptr + 1; /* set beginning of value */
2187 token_value_separator_found = TRUE;
2193 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2194 if (token_value_separator_found)
2196 if (!token_value_separator_warning)
2198 Error(ERR_INFO_LINE, "-");
2199 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2200 Error(ERR_INFO, "- config file: '%s'", filename);
2202 token_value_separator_warning = TRUE;
2205 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2211 /* cut trailing whitespaces from token */
2212 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2213 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2216 /* cut leading whitespaces from value */
2217 for (; *value; value++)
2218 if (*value != ' ' && *value != '\t')
2223 value = "true"; /* treat tokens without value as "true" */
2228 if (strEqual(token, "include"))
2230 if (getHashEntry(include_filename_hash, value) == NULL)
2232 char *basepath = getBasePath(filename);
2233 char *basename = getBaseName(value);
2234 char *filename_include = getPath2(basepath, basename);
2237 Error(ERR_INFO, "[including file '%s']", filename_include);
2240 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2244 free(filename_include);
2248 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2254 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2256 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2265 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2266 if (token_value_separator_warning)
2267 Error(ERR_INFO_LINE, "-");
2270 if (token_count == 0)
2271 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2273 if (top_recursion_level)
2274 freeSetupFileHash(include_filename_hash);
2280 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2284 if (!(file = fopen(filename, MODE_WRITE)))
2286 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2291 BEGIN_HASH_ITERATION(hash, itr)
2293 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2294 HASH_ITERATION_VALUE(itr)));
2296 END_HASH_ITERATION(hash, itr)
2301 SetupFileList *loadSetupFileList(char *filename)
2303 SetupFileList *setup_file_list = newSetupFileList("", "");
2304 SetupFileList *first_valid_list_entry;
2306 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2308 freeSetupFileList(setup_file_list);
2313 first_valid_list_entry = setup_file_list->next;
2315 /* free empty list header */
2316 setup_file_list->next = NULL;
2317 freeSetupFileList(setup_file_list);
2319 return first_valid_list_entry;
2322 SetupFileHash *loadSetupFileHash(char *filename)
2324 SetupFileHash *setup_file_hash = newSetupFileHash();
2326 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2328 freeSetupFileHash(setup_file_hash);
2333 return setup_file_hash;
2336 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2337 char *filename, char *identifier)
2339 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2342 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2343 else if (!checkCookieString(value, identifier))
2344 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2348 /* ========================================================================= */
2349 /* setup file stuff */
2350 /* ========================================================================= */
2352 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2353 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2354 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2356 /* level directory info */
2357 #define LEVELINFO_TOKEN_IDENTIFIER 0
2358 #define LEVELINFO_TOKEN_NAME 1
2359 #define LEVELINFO_TOKEN_NAME_SORTING 2
2360 #define LEVELINFO_TOKEN_AUTHOR 3
2361 #define LEVELINFO_TOKEN_YEAR 4
2362 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2363 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2364 #define LEVELINFO_TOKEN_TESTED_BY 7
2365 #define LEVELINFO_TOKEN_LEVELS 8
2366 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2367 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2368 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2369 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2370 #define LEVELINFO_TOKEN_READONLY 13
2371 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2372 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2373 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2374 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2375 #define LEVELINFO_TOKEN_MUSIC_SET 18
2376 #define LEVELINFO_TOKEN_FILENAME 19
2377 #define LEVELINFO_TOKEN_FILETYPE 20
2378 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2379 #define LEVELINFO_TOKEN_HANDICAP 22
2380 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2382 #define NUM_LEVELINFO_TOKENS 24
2384 static LevelDirTree ldi;
2386 static struct TokenInfo levelinfo_tokens[] =
2388 /* level directory info */
2389 { TYPE_STRING, &ldi.identifier, "identifier" },
2390 { TYPE_STRING, &ldi.name, "name" },
2391 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2392 { TYPE_STRING, &ldi.author, "author" },
2393 { TYPE_STRING, &ldi.year, "year" },
2394 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2395 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2396 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2397 { TYPE_INTEGER, &ldi.levels, "levels" },
2398 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2399 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2400 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2401 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2402 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2403 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2404 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2405 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2406 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2407 { TYPE_STRING, &ldi.music_set, "music_set" },
2408 { TYPE_STRING, &ldi.level_filename, "filename" },
2409 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2410 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2411 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2412 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2415 static struct TokenInfo artworkinfo_tokens[] =
2417 /* artwork directory info */
2418 { TYPE_STRING, &ldi.identifier, "identifier" },
2419 { TYPE_STRING, &ldi.subdir, "subdir" },
2420 { TYPE_STRING, &ldi.name, "name" },
2421 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2422 { TYPE_STRING, &ldi.author, "author" },
2423 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2424 { TYPE_STRING, &ldi.basepath, "basepath" },
2425 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2426 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2427 { TYPE_INTEGER, &ldi.color, "color" },
2428 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2433 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2437 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2438 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2439 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2440 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2443 ti->node_parent = NULL;
2444 ti->node_group = NULL;
2451 ti->fullpath = NULL;
2452 ti->basepath = NULL;
2453 ti->identifier = NULL;
2454 ti->name = getStringCopy(ANONYMOUS_NAME);
2455 ti->name_sorting = NULL;
2456 ti->author = getStringCopy(ANONYMOUS_NAME);
2459 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2460 ti->latest_engine = FALSE; /* default: get from level */
2461 ti->parent_link = FALSE;
2462 ti->in_user_dir = FALSE;
2463 ti->user_defined = FALSE;
2465 ti->class_desc = NULL;
2467 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2469 if (ti->type == TREE_TYPE_LEVEL_DIR)
2471 ti->imported_from = NULL;
2472 ti->imported_by = NULL;
2473 ti->tested_by = NULL;
2475 ti->graphics_set_ecs = NULL;
2476 ti->graphics_set_aga = NULL;
2477 ti->graphics_set = NULL;
2478 ti->sounds_set = NULL;
2479 ti->music_set = NULL;
2480 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2481 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2482 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2484 ti->level_filename = NULL;
2485 ti->level_filetype = NULL;
2487 ti->special_flags = NULL;
2490 ti->first_level = 0;
2492 ti->level_group = FALSE;
2493 ti->handicap_level = 0;
2494 ti->readonly = TRUE;
2495 ti->handicap = TRUE;
2496 ti->skip_levels = FALSE;
2500 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2504 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2506 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2511 /* copy all values from the parent structure */
2513 ti->type = parent->type;
2515 ti->node_top = parent->node_top;
2516 ti->node_parent = parent;
2517 ti->node_group = NULL;
2524 ti->fullpath = NULL;
2525 ti->basepath = NULL;
2526 ti->identifier = NULL;
2527 ti->name = getStringCopy(ANONYMOUS_NAME);
2528 ti->name_sorting = NULL;
2529 ti->author = getStringCopy(parent->author);
2530 ti->year = getStringCopy(parent->year);
2532 ti->sort_priority = parent->sort_priority;
2533 ti->latest_engine = parent->latest_engine;
2534 ti->parent_link = FALSE;
2535 ti->in_user_dir = parent->in_user_dir;
2536 ti->user_defined = parent->user_defined;
2537 ti->color = parent->color;
2538 ti->class_desc = getStringCopy(parent->class_desc);
2540 ti->infotext = getStringCopy(parent->infotext);
2542 if (ti->type == TREE_TYPE_LEVEL_DIR)
2544 ti->imported_from = getStringCopy(parent->imported_from);
2545 ti->imported_by = getStringCopy(parent->imported_by);
2546 ti->tested_by = getStringCopy(parent->tested_by);
2548 ti->graphics_set_ecs = NULL;
2549 ti->graphics_set_aga = NULL;
2550 ti->graphics_set = NULL;
2551 ti->sounds_set = NULL;
2552 ti->music_set = NULL;
2553 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2554 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2555 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2557 ti->level_filename = NULL;
2558 ti->level_filetype = NULL;
2560 ti->special_flags = getStringCopy(parent->special_flags);
2563 ti->first_level = 0;
2565 ti->level_group = FALSE;
2566 ti->handicap_level = 0;
2567 ti->readonly = TRUE;
2568 ti->handicap = TRUE;
2569 ti->skip_levels = FALSE;
2573 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2575 TreeInfo *ti_copy = newTreeInfo();
2577 /* copy all values from the original structure */
2579 ti_copy->type = ti->type;
2581 ti_copy->node_top = ti->node_top;
2582 ti_copy->node_parent = ti->node_parent;
2583 ti_copy->node_group = ti->node_group;
2584 ti_copy->next = ti->next;
2586 ti_copy->cl_first = ti->cl_first;
2587 ti_copy->cl_cursor = ti->cl_cursor;
2589 ti_copy->subdir = getStringCopy(ti->subdir);
2590 ti_copy->fullpath = getStringCopy(ti->fullpath);
2591 ti_copy->basepath = getStringCopy(ti->basepath);
2592 ti_copy->identifier = getStringCopy(ti->identifier);
2593 ti_copy->name = getStringCopy(ti->name);
2594 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2595 ti_copy->author = getStringCopy(ti->author);
2596 ti_copy->year = getStringCopy(ti->year);
2597 ti_copy->imported_from = getStringCopy(ti->imported_from);
2598 ti_copy->imported_by = getStringCopy(ti->imported_by);
2599 ti_copy->tested_by = getStringCopy(ti->tested_by);
2601 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2602 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2603 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2604 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2605 ti_copy->music_set = getStringCopy(ti->music_set);
2606 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2607 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2608 ti_copy->music_path = getStringCopy(ti->music_path);
2610 ti_copy->level_filename = getStringCopy(ti->level_filename);
2611 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2613 ti_copy->special_flags = getStringCopy(ti->special_flags);
2615 ti_copy->levels = ti->levels;
2616 ti_copy->first_level = ti->first_level;
2617 ti_copy->last_level = ti->last_level;
2618 ti_copy->sort_priority = ti->sort_priority;
2620 ti_copy->latest_engine = ti->latest_engine;
2622 ti_copy->level_group = ti->level_group;
2623 ti_copy->parent_link = ti->parent_link;
2624 ti_copy->in_user_dir = ti->in_user_dir;
2625 ti_copy->user_defined = ti->user_defined;
2626 ti_copy->readonly = ti->readonly;
2627 ti_copy->handicap = ti->handicap;
2628 ti_copy->skip_levels = ti->skip_levels;
2630 ti_copy->color = ti->color;
2631 ti_copy->class_desc = getStringCopy(ti->class_desc);
2632 ti_copy->handicap_level = ti->handicap_level;
2634 ti_copy->infotext = getStringCopy(ti->infotext);
2639 static void freeTreeInfo(TreeInfo *ti)
2644 checked_free(ti->subdir);
2645 checked_free(ti->fullpath);
2646 checked_free(ti->basepath);
2647 checked_free(ti->identifier);
2649 checked_free(ti->name);
2650 checked_free(ti->name_sorting);
2651 checked_free(ti->author);
2652 checked_free(ti->year);
2654 checked_free(ti->class_desc);
2656 checked_free(ti->infotext);
2658 if (ti->type == TREE_TYPE_LEVEL_DIR)
2660 checked_free(ti->imported_from);
2661 checked_free(ti->imported_by);
2662 checked_free(ti->tested_by);
2664 checked_free(ti->graphics_set_ecs);
2665 checked_free(ti->graphics_set_aga);
2666 checked_free(ti->graphics_set);
2667 checked_free(ti->sounds_set);
2668 checked_free(ti->music_set);
2670 checked_free(ti->graphics_path);
2671 checked_free(ti->sounds_path);
2672 checked_free(ti->music_path);
2674 checked_free(ti->level_filename);
2675 checked_free(ti->level_filetype);
2677 checked_free(ti->special_flags);
2683 void setSetupInfo(struct TokenInfo *token_info,
2684 int token_nr, char *token_value)
2686 int token_type = token_info[token_nr].type;
2687 void *setup_value = token_info[token_nr].value;
2689 if (token_value == NULL)
2692 /* set setup field to corresponding token value */
2697 *(boolean *)setup_value = get_boolean_from_string(token_value);
2701 *(int *)setup_value = get_switch3_from_string(token_value);
2705 *(Key *)setup_value = getKeyFromKeyName(token_value);
2709 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2713 *(int *)setup_value = get_integer_from_string(token_value);
2717 checked_free(*(char **)setup_value);
2718 *(char **)setup_value = getStringCopy(token_value);
2726 static int compareTreeInfoEntries(const void *object1, const void *object2)
2728 const TreeInfo *entry1 = *((TreeInfo **)object1);
2729 const TreeInfo *entry2 = *((TreeInfo **)object2);
2730 int class_sorting1, class_sorting2;
2733 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2735 class_sorting1 = LEVELSORTING(entry1);
2736 class_sorting2 = LEVELSORTING(entry2);
2740 class_sorting1 = ARTWORKSORTING(entry1);
2741 class_sorting2 = ARTWORKSORTING(entry2);
2744 if (entry1->parent_link || entry2->parent_link)
2745 compare_result = (entry1->parent_link ? -1 : +1);
2746 else if (entry1->sort_priority == entry2->sort_priority)
2748 char *name1 = getStringToLower(entry1->name_sorting);
2749 char *name2 = getStringToLower(entry2->name_sorting);
2751 compare_result = strcmp(name1, name2);
2756 else if (class_sorting1 == class_sorting2)
2757 compare_result = entry1->sort_priority - entry2->sort_priority;
2759 compare_result = class_sorting1 - class_sorting2;
2761 return compare_result;
2764 static void createParentTreeInfoNode(TreeInfo *node_parent)
2768 if (node_parent == NULL)
2771 ti_new = newTreeInfo();
2772 setTreeInfoToDefaults(ti_new, node_parent->type);
2774 ti_new->node_parent = node_parent;
2775 ti_new->parent_link = TRUE;
2777 setString(&ti_new->identifier, node_parent->identifier);
2778 setString(&ti_new->name, ".. (parent directory)");
2779 setString(&ti_new->name_sorting, ti_new->name);
2781 setString(&ti_new->subdir, "..");
2782 setString(&ti_new->fullpath, node_parent->fullpath);
2784 ti_new->sort_priority = node_parent->sort_priority;
2785 ti_new->latest_engine = node_parent->latest_engine;
2787 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2789 pushTreeInfo(&node_parent->node_group, ti_new);
2793 /* -------------------------------------------------------------------------- */
2794 /* functions for handling level and custom artwork info cache */
2795 /* -------------------------------------------------------------------------- */
2797 static void LoadArtworkInfoCache()
2799 InitCacheDirectory();
2801 if (artworkinfo_cache_old == NULL)
2803 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2805 /* try to load artwork info hash from already existing cache file */
2806 artworkinfo_cache_old = loadSetupFileHash(filename);
2808 /* if no artwork info cache file was found, start with empty hash */
2809 if (artworkinfo_cache_old == NULL)
2810 artworkinfo_cache_old = newSetupFileHash();
2815 if (artworkinfo_cache_new == NULL)
2816 artworkinfo_cache_new = newSetupFileHash();
2819 static void SaveArtworkInfoCache()
2821 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2823 InitCacheDirectory();
2825 saveSetupFileHash(artworkinfo_cache_new, filename);
2830 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2832 static char *prefix = NULL;
2834 checked_free(prefix);
2836 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2841 /* (identical to above function, but separate string buffer needed -- nasty) */
2842 static char *getCacheToken(char *prefix, char *suffix)
2844 static char *token = NULL;
2846 checked_free(token);
2848 token = getStringCat2WithSeparator(prefix, suffix, ".");
2853 static char *getFileTimestamp(char *filename)
2855 struct stat file_status;
2857 if (stat(filename, &file_status) != 0) /* cannot stat file */
2858 return getStringCopy(i_to_a(0));
2860 return getStringCopy(i_to_a(file_status.st_mtime));
2863 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2865 struct stat file_status;
2867 if (timestamp_string == NULL)
2870 if (stat(filename, &file_status) != 0) /* cannot stat file */
2873 return (file_status.st_mtime != atoi(timestamp_string));
2876 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2878 char *identifier = level_node->subdir;
2879 char *type_string = ARTWORK_DIRECTORY(type);
2880 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2881 char *token_main = getCacheToken(token_prefix, "CACHED");
2882 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2883 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2884 TreeInfo *artwork_info = NULL;
2886 if (!use_artworkinfo_cache)
2893 artwork_info = newTreeInfo();
2894 setTreeInfoToDefaults(artwork_info, type);
2896 /* set all structure fields according to the token/value pairs */
2897 ldi = *artwork_info;
2898 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2900 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2901 char *value = getHashEntry(artworkinfo_cache_old, token);
2903 setSetupInfo(artworkinfo_tokens, i, value);
2905 /* check if cache entry for this item is invalid or incomplete */
2909 Error(ERR_WARN, "cache entry '%s' invalid", token);
2916 *artwork_info = ldi;
2921 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2922 LEVELINFO_FILENAME);
2923 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2924 ARTWORKINFO_FILENAME(type));
2926 /* check if corresponding "levelinfo.conf" file has changed */
2927 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2928 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2930 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2933 /* check if corresponding "<artworkinfo>.conf" file has changed */
2934 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2935 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2937 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2942 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2945 checked_free(filename_levelinfo);
2946 checked_free(filename_artworkinfo);
2949 if (!cached && artwork_info != NULL)
2951 freeTreeInfo(artwork_info);
2956 return artwork_info;
2959 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2960 LevelDirTree *level_node, int type)
2962 char *identifier = level_node->subdir;
2963 char *type_string = ARTWORK_DIRECTORY(type);
2964 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2965 char *token_main = getCacheToken(token_prefix, "CACHED");
2966 boolean set_cache_timestamps = TRUE;
2969 setHashEntry(artworkinfo_cache_new, token_main, "true");
2971 if (set_cache_timestamps)
2973 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2974 LEVELINFO_FILENAME);
2975 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2976 ARTWORKINFO_FILENAME(type));
2977 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2978 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2980 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2981 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2983 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2984 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2986 checked_free(filename_levelinfo);
2987 checked_free(filename_artworkinfo);
2988 checked_free(timestamp_levelinfo);
2989 checked_free(timestamp_artworkinfo);
2992 ldi = *artwork_info;
2993 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2995 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2996 char *value = getSetupValue(artworkinfo_tokens[i].type,
2997 artworkinfo_tokens[i].value);
2999 setHashEntry(artworkinfo_cache_new, token, value);
3004 /* -------------------------------------------------------------------------- */
3005 /* functions for loading level info and custom artwork info */
3006 /* -------------------------------------------------------------------------- */
3008 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3009 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3011 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3012 TreeInfo *node_parent,
3013 char *level_directory,
3014 char *directory_name)
3017 static unsigned long progress_delay = 0;
3018 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3020 char *directory_path = getPath2(level_directory, directory_name);
3021 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3022 SetupFileHash *setup_file_hash;
3023 LevelDirTree *leveldir_new = NULL;
3026 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3027 if (!options.debug && !fileExists(filename))
3029 free(directory_path);
3035 setup_file_hash = loadSetupFileHash(filename);
3037 if (setup_file_hash == NULL)
3039 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3041 free(directory_path);
3047 leveldir_new = newTreeInfo();
3050 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3052 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3054 leveldir_new->subdir = getStringCopy(directory_name);
3056 checkSetupFileHashIdentifier(setup_file_hash, filename,
3057 getCookie("LEVELINFO"));
3059 /* set all structure fields according to the token/value pairs */
3060 ldi = *leveldir_new;
3061 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3062 setSetupInfo(levelinfo_tokens, i,
3063 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3064 *leveldir_new = ldi;
3066 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3067 setString(&leveldir_new->name, leveldir_new->subdir);
3069 if (leveldir_new->identifier == NULL)
3070 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3072 if (leveldir_new->name_sorting == NULL)
3073 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3075 if (node_parent == NULL) /* top level group */
3077 leveldir_new->basepath = getStringCopy(level_directory);
3078 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3080 else /* sub level group */
3082 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3083 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3087 if (leveldir_new->levels < 1)
3088 leveldir_new->levels = 1;
3091 leveldir_new->last_level =
3092 leveldir_new->first_level + leveldir_new->levels - 1;
3094 leveldir_new->in_user_dir =
3095 (!strEqual(leveldir_new->basepath, options.level_directory));
3097 /* adjust some settings if user's private level directory was detected */
3098 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3099 leveldir_new->in_user_dir &&
3100 (strEqual(leveldir_new->subdir, getLoginName()) ||
3101 strEqual(leveldir_new->name, getLoginName()) ||
3102 strEqual(leveldir_new->author, getRealName())))
3104 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3105 leveldir_new->readonly = FALSE;
3108 leveldir_new->user_defined =
3109 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3111 leveldir_new->color = LEVELCOLOR(leveldir_new);
3113 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3115 leveldir_new->handicap_level = /* set handicap to default value */
3116 (leveldir_new->user_defined || !leveldir_new->handicap ?
3117 leveldir_new->last_level : leveldir_new->first_level);
3121 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3122 leveldir_new->level_group);
3124 if (leveldir_new->level_group ||
3125 DelayReached(&progress_delay, progress_delay_value))
3126 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3129 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3133 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3135 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3137 /* skip level sets without levels (which are probably artwork base sets) */
3139 freeSetupFileHash(setup_file_hash);
3140 free(directory_path);
3148 pushTreeInfo(node_first, leveldir_new);
3150 freeSetupFileHash(setup_file_hash);
3152 if (leveldir_new->level_group)
3154 /* create node to link back to current level directory */
3155 createParentTreeInfoNode(leveldir_new);
3157 /* recursively step into sub-directory and look for more level series */
3158 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3159 leveldir_new, directory_path);
3162 free(directory_path);
3168 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3169 TreeInfo *node_parent,
3170 char *level_directory)
3173 struct dirent *dir_entry;
3174 boolean valid_entry_found = FALSE;
3176 if ((dir = opendir(level_directory)) == NULL)
3178 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3182 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3184 struct stat file_status;
3185 char *directory_name = dir_entry->d_name;
3186 char *directory_path = getPath2(level_directory, directory_name);
3188 /* skip entries for current and parent directory */
3189 if (strEqual(directory_name, ".") ||
3190 strEqual(directory_name, ".."))
3192 free(directory_path);
3196 /* find out if directory entry is itself a directory */
3197 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3198 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3200 free(directory_path);
3204 free(directory_path);
3206 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3207 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3208 strEqual(directory_name, MUSIC_DIRECTORY))
3211 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3218 /* special case: top level directory may directly contain "levelinfo.conf" */
3219 if (node_parent == NULL && !valid_entry_found)
3221 /* check if this directory directly contains a file "levelinfo.conf" */
3222 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3223 level_directory, ".");
3226 if (!valid_entry_found)
3227 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3231 boolean AdjustGraphicsForEMC()
3233 boolean settings_changed = FALSE;
3235 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3236 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3238 return settings_changed;
3241 void LoadLevelInfo()
3243 InitUserLevelDirectory(getLoginName());
3245 DrawInitText("Loading level series", 120, FC_GREEN);
3247 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3248 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3250 /* after loading all level set information, clone the level directory tree
3251 and remove all level sets without levels (these may still contain artwork
3252 to be offered in the setup menu as "custom artwork", and are therefore
3253 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3254 leveldir_first_all = leveldir_first;
3255 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3257 AdjustGraphicsForEMC();
3259 /* before sorting, the first entries will be from the user directory */
3260 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3262 if (leveldir_first == NULL)
3263 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3265 sortTreeInfo(&leveldir_first);
3268 dumpTreeInfo(leveldir_first, 0);
3272 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3273 TreeInfo *node_parent,
3274 char *base_directory,
3275 char *directory_name, int type)
3277 char *directory_path = getPath2(base_directory, directory_name);
3278 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3279 SetupFileHash *setup_file_hash = NULL;
3280 TreeInfo *artwork_new = NULL;
3283 if (fileExists(filename))
3284 setup_file_hash = loadSetupFileHash(filename);
3286 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3289 struct dirent *dir_entry;
3290 boolean valid_file_found = FALSE;
3292 if ((dir = opendir(directory_path)) != NULL)
3294 while ((dir_entry = readdir(dir)) != NULL)
3296 char *entry_name = dir_entry->d_name;
3298 if (FileIsArtworkType(entry_name, type))
3300 valid_file_found = TRUE;
3308 if (!valid_file_found)
3310 if (!strEqual(directory_name, "."))
3311 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3313 free(directory_path);
3320 artwork_new = newTreeInfo();
3323 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3325 setTreeInfoToDefaults(artwork_new, type);
3327 artwork_new->subdir = getStringCopy(directory_name);
3329 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3332 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3335 /* set all structure fields according to the token/value pairs */
3337 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3338 setSetupInfo(levelinfo_tokens, i,
3339 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3342 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3343 setString(&artwork_new->name, artwork_new->subdir);
3345 if (artwork_new->identifier == NULL)
3346 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3348 if (artwork_new->name_sorting == NULL)
3349 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3352 if (node_parent == NULL) /* top level group */
3354 artwork_new->basepath = getStringCopy(base_directory);
3355 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3357 else /* sub level group */
3359 artwork_new->basepath = getStringCopy(node_parent->basepath);
3360 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3363 artwork_new->in_user_dir =
3364 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3366 /* (may use ".sort_priority" from "setup_file_hash" above) */
3367 artwork_new->color = ARTWORKCOLOR(artwork_new);
3369 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3371 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3373 if (strEqual(artwork_new->subdir, "."))
3375 if (artwork_new->user_defined)
3377 setString(&artwork_new->identifier, "private");
3378 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3382 setString(&artwork_new->identifier, "classic");
3383 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3386 /* set to new values after changing ".sort_priority" */
3387 artwork_new->color = ARTWORKCOLOR(artwork_new);
3389 setString(&artwork_new->class_desc,
3390 getLevelClassDescription(artwork_new));
3394 setString(&artwork_new->identifier, artwork_new->subdir);
3397 setString(&artwork_new->name, artwork_new->identifier);
3398 setString(&artwork_new->name_sorting, artwork_new->name);
3402 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3405 pushTreeInfo(node_first, artwork_new);
3407 freeSetupFileHash(setup_file_hash);
3409 free(directory_path);
3415 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3416 TreeInfo *node_parent,
3417 char *base_directory, int type)
3420 struct dirent *dir_entry;
3421 boolean valid_entry_found = FALSE;
3423 if ((dir = opendir(base_directory)) == NULL)
3425 /* display error if directory is main "options.graphics_directory" etc. */
3426 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3427 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3432 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3434 struct stat file_status;
3435 char *directory_name = dir_entry->d_name;
3436 char *directory_path = getPath2(base_directory, directory_name);
3438 /* skip directory entries for current and parent directory */
3439 if (strEqual(directory_name, ".") ||
3440 strEqual(directory_name, ".."))
3442 free(directory_path);
3446 /* skip directory entries which are not a directory or are not accessible */
3447 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3448 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3450 free(directory_path);
3454 free(directory_path);
3456 /* check if this directory contains artwork with or without config file */
3457 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3459 directory_name, type);
3464 /* check if this directory directly contains artwork itself */
3465 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3466 base_directory, ".",
3468 if (!valid_entry_found)
3469 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3473 static TreeInfo *getDummyArtworkInfo(int type)
3475 /* this is only needed when there is completely no artwork available */
3476 TreeInfo *artwork_new = newTreeInfo();
3478 setTreeInfoToDefaults(artwork_new, type);
3480 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3481 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3482 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3484 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3485 setString(&artwork_new->name, UNDEFINED_FILENAME);
3486 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3491 void LoadArtworkInfo()
3493 LoadArtworkInfoCache();
3495 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3497 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3498 options.graphics_directory,
3499 TREE_TYPE_GRAPHICS_DIR);
3500 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3501 getUserGraphicsDir(),
3502 TREE_TYPE_GRAPHICS_DIR);
3504 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3505 options.sounds_directory,
3506 TREE_TYPE_SOUNDS_DIR);
3507 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3509 TREE_TYPE_SOUNDS_DIR);
3511 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3512 options.music_directory,
3513 TREE_TYPE_MUSIC_DIR);
3514 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3516 TREE_TYPE_MUSIC_DIR);
3518 if (artwork.gfx_first == NULL)
3519 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3520 if (artwork.snd_first == NULL)
3521 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3522 if (artwork.mus_first == NULL)
3523 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3525 /* before sorting, the first entries will be from the user directory */
3526 artwork.gfx_current =
3527 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3528 if (artwork.gfx_current == NULL)
3529 artwork.gfx_current =
3530 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3531 if (artwork.gfx_current == NULL)
3532 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3534 artwork.snd_current =
3535 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3536 if (artwork.snd_current == NULL)
3537 artwork.snd_current =
3538 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3539 if (artwork.snd_current == NULL)
3540 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3542 artwork.mus_current =
3543 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3544 if (artwork.mus_current == NULL)
3545 artwork.mus_current =
3546 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3547 if (artwork.mus_current == NULL)
3548 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3550 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3551 artwork.snd_current_identifier = artwork.snd_current->identifier;
3552 artwork.mus_current_identifier = artwork.mus_current->identifier;
3555 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3556 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3557 printf("music set == %s\n\n", artwork.mus_current_identifier);
3560 sortTreeInfo(&artwork.gfx_first);
3561 sortTreeInfo(&artwork.snd_first);
3562 sortTreeInfo(&artwork.mus_first);
3565 dumpTreeInfo(artwork.gfx_first, 0);
3566 dumpTreeInfo(artwork.snd_first, 0);
3567 dumpTreeInfo(artwork.mus_first, 0);
3571 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3572 LevelDirTree *level_node)
3575 static unsigned long progress_delay = 0;
3576 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3578 int type = (*artwork_node)->type;
3580 /* recursively check all level directories for artwork sub-directories */
3584 /* check all tree entries for artwork, but skip parent link entries */
3585 if (!level_node->parent_link)
3587 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3588 boolean cached = (artwork_new != NULL);
3592 pushTreeInfo(artwork_node, artwork_new);
3596 TreeInfo *topnode_last = *artwork_node;
3597 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3598 ARTWORK_DIRECTORY(type));
3600 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3602 if (topnode_last != *artwork_node) /* check for newly added node */
3604 artwork_new = *artwork_node;
3606 setString(&artwork_new->identifier, level_node->subdir);
3607 setString(&artwork_new->name, level_node->name);
3608 setString(&artwork_new->name_sorting, level_node->name_sorting);
3610 artwork_new->sort_priority = level_node->sort_priority;
3611 artwork_new->color = LEVELCOLOR(artwork_new);
3617 /* insert artwork info (from old cache or filesystem) into new cache */
3618 if (artwork_new != NULL)
3619 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3623 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3624 level_node->level_group);
3626 if (level_node->level_group ||
3627 DelayReached(&progress_delay, progress_delay_value))
3628 DrawInitText(level_node->name, 150, FC_YELLOW);
3631 if (level_node->node_group != NULL)
3632 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3634 level_node = level_node->next;
3638 void LoadLevelArtworkInfo()
3640 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3642 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3643 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3644 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3646 SaveArtworkInfoCache();
3648 /* needed for reloading level artwork not known at ealier stage */
3650 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3652 artwork.gfx_current =
3653 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3654 if (artwork.gfx_current == NULL)
3655 artwork.gfx_current =
3656 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3657 if (artwork.gfx_current == NULL)
3658 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3661 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3663 artwork.snd_current =
3664 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3665 if (artwork.snd_current == NULL)
3666 artwork.snd_current =
3667 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3668 if (artwork.snd_current == NULL)
3669 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3672 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3674 artwork.mus_current =
3675 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3676 if (artwork.mus_current == NULL)
3677 artwork.mus_current =
3678 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3679 if (artwork.mus_current == NULL)
3680 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3683 sortTreeInfo(&artwork.gfx_first);
3684 sortTreeInfo(&artwork.snd_first);
3685 sortTreeInfo(&artwork.mus_first);
3688 dumpTreeInfo(artwork.gfx_first, 0);
3689 dumpTreeInfo(artwork.snd_first, 0);
3690 dumpTreeInfo(artwork.mus_first, 0);
3694 static void SaveUserLevelInfo()
3696 LevelDirTree *level_info;
3701 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3703 if (!(file = fopen(filename, MODE_WRITE)))
3705 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3710 level_info = newTreeInfo();
3712 /* always start with reliable default values */
3713 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3715 setString(&level_info->name, getLoginName());
3716 setString(&level_info->author, getRealName());
3717 level_info->levels = 100;
3718 level_info->first_level = 1;
3720 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3722 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3723 getCookie("LEVELINFO")));
3726 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3728 if (i == LEVELINFO_TOKEN_NAME ||
3729 i == LEVELINFO_TOKEN_AUTHOR ||
3730 i == LEVELINFO_TOKEN_LEVELS ||
3731 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3732 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3734 /* just to make things nicer :) */
3735 if (i == LEVELINFO_TOKEN_AUTHOR)
3736 fprintf(file, "\n");
3739 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3743 SetFilePermissions(filename, PERMS_PRIVATE);
3745 freeTreeInfo(level_info);
3749 char *getSetupValue(int type, void *value)
3751 static char value_string[MAX_LINE_LEN];
3759 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3763 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3767 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3768 *(int *)value == FALSE ? "off" : "on"));
3772 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3775 case TYPE_YES_NO_AUTO:
3776 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3777 *(int *)value == FALSE ? "no" : "yes"));
3781 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3785 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3789 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3793 sprintf(value_string, "%d", *(int *)value);
3797 if (*(char **)value == NULL)
3800 strcpy(value_string, *(char **)value);
3804 value_string[0] = '\0';
3808 if (type & TYPE_GHOSTED)
3809 strcpy(value_string, "n/a");
3811 return value_string;
3814 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3818 static char token_string[MAX_LINE_LEN];
3819 int token_type = token_info[token_nr].type;
3820 void *setup_value = token_info[token_nr].value;
3821 char *token_text = token_info[token_nr].text;
3822 char *value_string = getSetupValue(token_type, setup_value);
3824 /* build complete token string */
3825 sprintf(token_string, "%s%s", prefix, token_text);
3827 /* build setup entry line */
3828 line = getFormattedSetupEntry(token_string, value_string);
3830 if (token_type == TYPE_KEY_X11)
3832 Key key = *(Key *)setup_value;
3833 char *keyname = getKeyNameFromKey(key);
3835 /* add comment, if useful */
3836 if (!strEqual(keyname, "(undefined)") &&
3837 !strEqual(keyname, "(unknown)"))
3839 /* add at least one whitespace */
3841 for (i = strlen(line); i < token_comment_position; i++)
3845 strcat(line, keyname);
3852 void LoadLevelSetup_LastSeries()
3854 /* ----------------------------------------------------------------------- */
3855 /* ~/.<program>/levelsetup.conf */
3856 /* ----------------------------------------------------------------------- */
3858 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3859 SetupFileHash *level_setup_hash = NULL;
3861 /* always start with reliable default values */
3862 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3864 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3865 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3867 if (leveldir_current == NULL)
3868 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3871 if ((level_setup_hash = loadSetupFileHash(filename)))
3873 char *last_level_series =
3874 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3876 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3878 if (leveldir_current == NULL)
3879 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3881 checkSetupFileHashIdentifier(level_setup_hash, filename,
3882 getCookie("LEVELSETUP"));
3884 freeSetupFileHash(level_setup_hash);
3887 Error(ERR_WARN, "using default setup values");
3892 void SaveLevelSetup_LastSeries()
3894 /* ----------------------------------------------------------------------- */
3895 /* ~/.<program>/levelsetup.conf */
3896 /* ----------------------------------------------------------------------- */
3898 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3899 char *level_subdir = leveldir_current->subdir;
3902 InitUserDataDirectory();
3904 if (!(file = fopen(filename, MODE_WRITE)))
3906 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3911 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3912 getCookie("LEVELSETUP")));
3913 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3918 SetFilePermissions(filename, PERMS_PRIVATE);
3923 static void checkSeriesInfo()
3925 static char *level_directory = NULL;
3927 struct dirent *dir_entry;
3929 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3931 level_directory = getPath2((leveldir_current->in_user_dir ?
3932 getUserLevelDir(NULL) :
3933 options.level_directory),
3934 leveldir_current->fullpath);
3936 if ((dir = opendir(level_directory)) == NULL)
3938 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3942 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3944 if (strlen(dir_entry->d_name) > 4 &&
3945 dir_entry->d_name[3] == '.' &&
3946 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3948 char levelnum_str[4];
3951 strncpy(levelnum_str, dir_entry->d_name, 3);
3952 levelnum_str[3] = '\0';
3954 levelnum_value = atoi(levelnum_str);
3957 if (levelnum_value < leveldir_current->first_level)
3959 Error(ERR_WARN, "additional level %d found", levelnum_value);
3960 leveldir_current->first_level = levelnum_value;
3962 else if (levelnum_value > leveldir_current->last_level)
3964 Error(ERR_WARN, "additional level %d found", levelnum_value);
3965 leveldir_current->last_level = levelnum_value;
3974 void LoadLevelSetup_SeriesInfo()
3977 SetupFileHash *level_setup_hash = NULL;
3978 char *level_subdir = leveldir_current->subdir;
3980 /* always start with reliable default values */
3981 level_nr = leveldir_current->first_level;
3983 checkSeriesInfo(leveldir_current);
3985 /* ----------------------------------------------------------------------- */
3986 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3987 /* ----------------------------------------------------------------------- */
3989 level_subdir = leveldir_current->subdir;
3991 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3993 if ((level_setup_hash = loadSetupFileHash(filename)))
3997 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4001 level_nr = atoi(token_value);
4003 if (level_nr < leveldir_current->first_level)
4004 level_nr = leveldir_current->first_level;
4005 if (level_nr > leveldir_current->last_level)
4006 level_nr = leveldir_current->last_level;
4009 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4013 int level_nr = atoi(token_value);
4015 if (level_nr < leveldir_current->first_level)
4016 level_nr = leveldir_current->first_level;
4017 if (level_nr > leveldir_current->last_level + 1)
4018 level_nr = leveldir_current->last_level;
4020 if (leveldir_current->user_defined || !leveldir_current->handicap)
4021 level_nr = leveldir_current->last_level;
4023 leveldir_current->handicap_level = level_nr;
4026 checkSetupFileHashIdentifier(level_setup_hash, filename,
4027 getCookie("LEVELSETUP"));
4029 freeSetupFileHash(level_setup_hash);
4032 Error(ERR_WARN, "using default setup values");
4037 void SaveLevelSetup_SeriesInfo()
4040 char *level_subdir = leveldir_current->subdir;
4041 char *level_nr_str = int2str(level_nr, 0);
4042 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4045 /* ----------------------------------------------------------------------- */
4046 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4047 /* ----------------------------------------------------------------------- */
4049 InitLevelSetupDirectory(level_subdir);
4051 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4053 if (!(file = fopen(filename, MODE_WRITE)))
4055 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4060 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4061 getCookie("LEVELSETUP")));
4062 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4064 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4065 handicap_level_str));
4069 SetFilePermissions(filename, PERMS_PRIVATE);