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 *getFileTimestampString(char *filename)
2856 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2858 struct stat file_status;
2860 if (stat(filename, &file_status) != 0) /* cannot stat file */
2861 return getStringCopy(i_to_a(0));
2863 return getStringCopy(i_to_a(file_status.st_mtime));
2867 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2869 struct stat file_status;
2871 if (timestamp_string == NULL)
2874 if (stat(filename, &file_status) != 0) /* cannot stat file */
2877 return (file_status.st_mtime != atoi(timestamp_string));
2880 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2882 char *identifier = level_node->subdir;
2883 char *type_string = ARTWORK_DIRECTORY(type);
2884 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2885 char *token_main = getCacheToken(token_prefix, "CACHED");
2886 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2887 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2888 TreeInfo *artwork_info = NULL;
2890 if (!use_artworkinfo_cache)
2897 artwork_info = newTreeInfo();
2898 setTreeInfoToDefaults(artwork_info, type);
2900 /* set all structure fields according to the token/value pairs */
2901 ldi = *artwork_info;
2902 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2904 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2905 char *value = getHashEntry(artworkinfo_cache_old, token);
2907 setSetupInfo(artworkinfo_tokens, i, value);
2909 /* check if cache entry for this item is invalid or incomplete */
2913 Error(ERR_WARN, "cache entry '%s' invalid", token);
2920 *artwork_info = ldi;
2925 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2926 LEVELINFO_FILENAME);
2927 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2928 ARTWORKINFO_FILENAME(type));
2930 /* check if corresponding "levelinfo.conf" file has changed */
2931 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2932 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2934 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2937 /* check if corresponding "<artworkinfo>.conf" file has changed */
2938 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2939 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2941 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2946 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2949 checked_free(filename_levelinfo);
2950 checked_free(filename_artworkinfo);
2953 if (!cached && artwork_info != NULL)
2955 freeTreeInfo(artwork_info);
2960 return artwork_info;
2963 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2964 LevelDirTree *level_node, int type)
2966 char *identifier = level_node->subdir;
2967 char *type_string = ARTWORK_DIRECTORY(type);
2968 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2969 char *token_main = getCacheToken(token_prefix, "CACHED");
2970 boolean set_cache_timestamps = TRUE;
2973 setHashEntry(artworkinfo_cache_new, token_main, "true");
2975 if (set_cache_timestamps)
2977 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2978 LEVELINFO_FILENAME);
2979 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2980 ARTWORKINFO_FILENAME(type));
2981 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2982 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2984 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2985 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2987 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2988 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2990 checked_free(filename_levelinfo);
2991 checked_free(filename_artworkinfo);
2992 checked_free(timestamp_levelinfo);
2993 checked_free(timestamp_artworkinfo);
2996 ldi = *artwork_info;
2997 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2999 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3000 char *value = getSetupValue(artworkinfo_tokens[i].type,
3001 artworkinfo_tokens[i].value);
3003 setHashEntry(artworkinfo_cache_new, token, value);
3008 /* -------------------------------------------------------------------------- */
3009 /* functions for loading level info and custom artwork info */
3010 /* -------------------------------------------------------------------------- */
3012 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3013 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3015 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3016 TreeInfo *node_parent,
3017 char *level_directory,
3018 char *directory_name)
3021 static unsigned long progress_delay = 0;
3022 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3024 char *directory_path = getPath2(level_directory, directory_name);
3025 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3026 SetupFileHash *setup_file_hash;
3027 LevelDirTree *leveldir_new = NULL;
3030 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3031 if (!options.debug && !fileExists(filename))
3033 free(directory_path);
3039 setup_file_hash = loadSetupFileHash(filename);
3041 if (setup_file_hash == NULL)
3043 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3045 free(directory_path);
3051 leveldir_new = newTreeInfo();
3054 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3056 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3058 leveldir_new->subdir = getStringCopy(directory_name);
3060 checkSetupFileHashIdentifier(setup_file_hash, filename,
3061 getCookie("LEVELINFO"));
3063 /* set all structure fields according to the token/value pairs */
3064 ldi = *leveldir_new;
3065 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3066 setSetupInfo(levelinfo_tokens, i,
3067 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3068 *leveldir_new = ldi;
3070 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3071 setString(&leveldir_new->name, leveldir_new->subdir);
3073 if (leveldir_new->identifier == NULL)
3074 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3076 if (leveldir_new->name_sorting == NULL)
3077 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3079 if (node_parent == NULL) /* top level group */
3081 leveldir_new->basepath = getStringCopy(level_directory);
3082 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3084 else /* sub level group */
3086 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3087 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3091 if (leveldir_new->levels < 1)
3092 leveldir_new->levels = 1;
3095 leveldir_new->last_level =
3096 leveldir_new->first_level + leveldir_new->levels - 1;
3098 leveldir_new->in_user_dir =
3099 (!strEqual(leveldir_new->basepath, options.level_directory));
3101 /* adjust some settings if user's private level directory was detected */
3102 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3103 leveldir_new->in_user_dir &&
3104 (strEqual(leveldir_new->subdir, getLoginName()) ||
3105 strEqual(leveldir_new->name, getLoginName()) ||
3106 strEqual(leveldir_new->author, getRealName())))
3108 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3109 leveldir_new->readonly = FALSE;
3112 leveldir_new->user_defined =
3113 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3115 leveldir_new->color = LEVELCOLOR(leveldir_new);
3117 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3119 leveldir_new->handicap_level = /* set handicap to default value */
3120 (leveldir_new->user_defined || !leveldir_new->handicap ?
3121 leveldir_new->last_level : leveldir_new->first_level);
3125 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3126 leveldir_new->level_group);
3128 if (leveldir_new->level_group ||
3129 DelayReached(&progress_delay, progress_delay_value))
3130 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3133 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3137 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3139 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3141 /* skip level sets without levels (which are probably artwork base sets) */
3143 freeSetupFileHash(setup_file_hash);
3144 free(directory_path);
3152 pushTreeInfo(node_first, leveldir_new);
3154 freeSetupFileHash(setup_file_hash);
3156 if (leveldir_new->level_group)
3158 /* create node to link back to current level directory */
3159 createParentTreeInfoNode(leveldir_new);
3161 /* recursively step into sub-directory and look for more level series */
3162 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3163 leveldir_new, directory_path);
3166 free(directory_path);
3172 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3173 TreeInfo *node_parent,
3174 char *level_directory)
3177 struct dirent *dir_entry;
3178 boolean valid_entry_found = FALSE;
3180 if ((dir = opendir(level_directory)) == NULL)
3182 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3186 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3188 struct stat file_status;
3189 char *directory_name = dir_entry->d_name;
3190 char *directory_path = getPath2(level_directory, directory_name);
3192 /* skip entries for current and parent directory */
3193 if (strEqual(directory_name, ".") ||
3194 strEqual(directory_name, ".."))
3196 free(directory_path);
3200 /* find out if directory entry is itself a directory */
3201 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3202 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3204 free(directory_path);
3208 free(directory_path);
3210 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3211 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3212 strEqual(directory_name, MUSIC_DIRECTORY))
3215 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3222 /* special case: top level directory may directly contain "levelinfo.conf" */
3223 if (node_parent == NULL && !valid_entry_found)
3225 /* check if this directory directly contains a file "levelinfo.conf" */
3226 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3227 level_directory, ".");
3230 if (!valid_entry_found)
3231 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3235 boolean AdjustGraphicsForEMC()
3237 boolean settings_changed = FALSE;
3239 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3240 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3242 return settings_changed;
3245 void LoadLevelInfo()
3247 InitUserLevelDirectory(getLoginName());
3249 DrawInitText("Loading level series", 120, FC_GREEN);
3251 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3252 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3254 /* after loading all level set information, clone the level directory tree
3255 and remove all level sets without levels (these may still contain artwork
3256 to be offered in the setup menu as "custom artwork", and are therefore
3257 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3258 leveldir_first_all = leveldir_first;
3259 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3261 AdjustGraphicsForEMC();
3263 /* before sorting, the first entries will be from the user directory */
3264 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3266 if (leveldir_first == NULL)
3267 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3269 sortTreeInfo(&leveldir_first);
3272 dumpTreeInfo(leveldir_first, 0);
3276 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3277 TreeInfo *node_parent,
3278 char *base_directory,
3279 char *directory_name, int type)
3281 char *directory_path = getPath2(base_directory, directory_name);
3282 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3283 SetupFileHash *setup_file_hash = NULL;
3284 TreeInfo *artwork_new = NULL;
3287 if (fileExists(filename))
3288 setup_file_hash = loadSetupFileHash(filename);
3290 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3293 struct dirent *dir_entry;
3294 boolean valid_file_found = FALSE;
3296 if ((dir = opendir(directory_path)) != NULL)
3298 while ((dir_entry = readdir(dir)) != NULL)
3300 char *entry_name = dir_entry->d_name;
3302 if (FileIsArtworkType(entry_name, type))
3304 valid_file_found = TRUE;
3312 if (!valid_file_found)
3314 if (!strEqual(directory_name, "."))
3315 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3317 free(directory_path);
3324 artwork_new = newTreeInfo();
3327 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3329 setTreeInfoToDefaults(artwork_new, type);
3331 artwork_new->subdir = getStringCopy(directory_name);
3333 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3336 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3339 /* set all structure fields according to the token/value pairs */
3341 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3342 setSetupInfo(levelinfo_tokens, i,
3343 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3346 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3347 setString(&artwork_new->name, artwork_new->subdir);
3349 if (artwork_new->identifier == NULL)
3350 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3352 if (artwork_new->name_sorting == NULL)
3353 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3356 if (node_parent == NULL) /* top level group */
3358 artwork_new->basepath = getStringCopy(base_directory);
3359 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3361 else /* sub level group */
3363 artwork_new->basepath = getStringCopy(node_parent->basepath);
3364 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3367 artwork_new->in_user_dir =
3368 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3370 /* (may use ".sort_priority" from "setup_file_hash" above) */
3371 artwork_new->color = ARTWORKCOLOR(artwork_new);
3373 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3375 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3377 if (strEqual(artwork_new->subdir, "."))
3379 if (artwork_new->user_defined)
3381 setString(&artwork_new->identifier, "private");
3382 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3386 setString(&artwork_new->identifier, "classic");
3387 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3390 /* set to new values after changing ".sort_priority" */
3391 artwork_new->color = ARTWORKCOLOR(artwork_new);
3393 setString(&artwork_new->class_desc,
3394 getLevelClassDescription(artwork_new));
3398 setString(&artwork_new->identifier, artwork_new->subdir);
3401 setString(&artwork_new->name, artwork_new->identifier);
3402 setString(&artwork_new->name_sorting, artwork_new->name);
3406 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3409 pushTreeInfo(node_first, artwork_new);
3411 freeSetupFileHash(setup_file_hash);
3413 free(directory_path);
3419 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3420 TreeInfo *node_parent,
3421 char *base_directory, int type)
3424 struct dirent *dir_entry;
3425 boolean valid_entry_found = FALSE;
3427 if ((dir = opendir(base_directory)) == NULL)
3429 /* display error if directory is main "options.graphics_directory" etc. */
3430 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3431 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3436 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3438 struct stat file_status;
3439 char *directory_name = dir_entry->d_name;
3440 char *directory_path = getPath2(base_directory, directory_name);
3442 /* skip directory entries for current and parent directory */
3443 if (strEqual(directory_name, ".") ||
3444 strEqual(directory_name, ".."))
3446 free(directory_path);
3450 /* skip directory entries which are not a directory or are not accessible */
3451 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3452 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3454 free(directory_path);
3458 free(directory_path);
3460 /* check if this directory contains artwork with or without config file */
3461 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3463 directory_name, type);
3468 /* check if this directory directly contains artwork itself */
3469 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3470 base_directory, ".",
3472 if (!valid_entry_found)
3473 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3477 static TreeInfo *getDummyArtworkInfo(int type)
3479 /* this is only needed when there is completely no artwork available */
3480 TreeInfo *artwork_new = newTreeInfo();
3482 setTreeInfoToDefaults(artwork_new, type);
3484 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3485 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3486 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3488 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3489 setString(&artwork_new->name, UNDEFINED_FILENAME);
3490 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3495 void LoadArtworkInfo()
3497 LoadArtworkInfoCache();
3499 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3501 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3502 options.graphics_directory,
3503 TREE_TYPE_GRAPHICS_DIR);
3504 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3505 getUserGraphicsDir(),
3506 TREE_TYPE_GRAPHICS_DIR);
3508 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3509 options.sounds_directory,
3510 TREE_TYPE_SOUNDS_DIR);
3511 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3513 TREE_TYPE_SOUNDS_DIR);
3515 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3516 options.music_directory,
3517 TREE_TYPE_MUSIC_DIR);
3518 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3520 TREE_TYPE_MUSIC_DIR);
3522 if (artwork.gfx_first == NULL)
3523 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3524 if (artwork.snd_first == NULL)
3525 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3526 if (artwork.mus_first == NULL)
3527 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3529 /* before sorting, the first entries will be from the user directory */
3530 artwork.gfx_current =
3531 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3532 if (artwork.gfx_current == NULL)
3533 artwork.gfx_current =
3534 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3535 if (artwork.gfx_current == NULL)
3536 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3538 artwork.snd_current =
3539 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3540 if (artwork.snd_current == NULL)
3541 artwork.snd_current =
3542 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3543 if (artwork.snd_current == NULL)
3544 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3546 artwork.mus_current =
3547 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3548 if (artwork.mus_current == NULL)
3549 artwork.mus_current =
3550 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3551 if (artwork.mus_current == NULL)
3552 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3554 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3555 artwork.snd_current_identifier = artwork.snd_current->identifier;
3556 artwork.mus_current_identifier = artwork.mus_current->identifier;
3559 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3560 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3561 printf("music set == %s\n\n", artwork.mus_current_identifier);
3564 sortTreeInfo(&artwork.gfx_first);
3565 sortTreeInfo(&artwork.snd_first);
3566 sortTreeInfo(&artwork.mus_first);
3569 dumpTreeInfo(artwork.gfx_first, 0);
3570 dumpTreeInfo(artwork.snd_first, 0);
3571 dumpTreeInfo(artwork.mus_first, 0);
3575 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3576 LevelDirTree *level_node)
3579 static unsigned long progress_delay = 0;
3580 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3582 int type = (*artwork_node)->type;
3584 /* recursively check all level directories for artwork sub-directories */
3588 /* check all tree entries for artwork, but skip parent link entries */
3589 if (!level_node->parent_link)
3591 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3592 boolean cached = (artwork_new != NULL);
3596 pushTreeInfo(artwork_node, artwork_new);
3600 TreeInfo *topnode_last = *artwork_node;
3601 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3602 ARTWORK_DIRECTORY(type));
3604 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3606 if (topnode_last != *artwork_node) /* check for newly added node */
3608 artwork_new = *artwork_node;
3610 setString(&artwork_new->identifier, level_node->subdir);
3611 setString(&artwork_new->name, level_node->name);
3612 setString(&artwork_new->name_sorting, level_node->name_sorting);
3614 artwork_new->sort_priority = level_node->sort_priority;
3615 artwork_new->color = LEVELCOLOR(artwork_new);
3621 /* insert artwork info (from old cache or filesystem) into new cache */
3622 if (artwork_new != NULL)
3623 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3627 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3628 level_node->level_group);
3630 if (level_node->level_group ||
3631 DelayReached(&progress_delay, progress_delay_value))
3632 DrawInitText(level_node->name, 150, FC_YELLOW);
3635 if (level_node->node_group != NULL)
3636 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3638 level_node = level_node->next;
3642 void LoadLevelArtworkInfo()
3644 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3646 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3647 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3648 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3650 SaveArtworkInfoCache();
3652 /* needed for reloading level artwork not known at ealier stage */
3654 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3656 artwork.gfx_current =
3657 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3658 if (artwork.gfx_current == NULL)
3659 artwork.gfx_current =
3660 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3661 if (artwork.gfx_current == NULL)
3662 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3665 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3667 artwork.snd_current =
3668 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3669 if (artwork.snd_current == NULL)
3670 artwork.snd_current =
3671 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3672 if (artwork.snd_current == NULL)
3673 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3676 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3678 artwork.mus_current =
3679 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3680 if (artwork.mus_current == NULL)
3681 artwork.mus_current =
3682 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3683 if (artwork.mus_current == NULL)
3684 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3687 sortTreeInfo(&artwork.gfx_first);
3688 sortTreeInfo(&artwork.snd_first);
3689 sortTreeInfo(&artwork.mus_first);
3692 dumpTreeInfo(artwork.gfx_first, 0);
3693 dumpTreeInfo(artwork.snd_first, 0);
3694 dumpTreeInfo(artwork.mus_first, 0);
3698 static void SaveUserLevelInfo()
3700 LevelDirTree *level_info;
3705 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3707 if (!(file = fopen(filename, MODE_WRITE)))
3709 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3714 level_info = newTreeInfo();
3716 /* always start with reliable default values */
3717 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3719 setString(&level_info->name, getLoginName());
3720 setString(&level_info->author, getRealName());
3721 level_info->levels = 100;
3722 level_info->first_level = 1;
3724 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3726 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3727 getCookie("LEVELINFO")));
3730 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3732 if (i == LEVELINFO_TOKEN_NAME ||
3733 i == LEVELINFO_TOKEN_AUTHOR ||
3734 i == LEVELINFO_TOKEN_LEVELS ||
3735 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3736 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3738 /* just to make things nicer :) */
3739 if (i == LEVELINFO_TOKEN_AUTHOR)
3740 fprintf(file, "\n");
3743 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3747 SetFilePermissions(filename, PERMS_PRIVATE);
3749 freeTreeInfo(level_info);
3753 char *getSetupValue(int type, void *value)
3755 static char value_string[MAX_LINE_LEN];
3763 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3767 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3771 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3772 *(int *)value == FALSE ? "off" : "on"));
3776 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3779 case TYPE_YES_NO_AUTO:
3780 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3781 *(int *)value == FALSE ? "no" : "yes"));
3785 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3789 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3793 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3797 sprintf(value_string, "%d", *(int *)value);
3801 if (*(char **)value == NULL)
3804 strcpy(value_string, *(char **)value);
3808 value_string[0] = '\0';
3812 if (type & TYPE_GHOSTED)
3813 strcpy(value_string, "n/a");
3815 return value_string;
3818 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3822 static char token_string[MAX_LINE_LEN];
3823 int token_type = token_info[token_nr].type;
3824 void *setup_value = token_info[token_nr].value;
3825 char *token_text = token_info[token_nr].text;
3826 char *value_string = getSetupValue(token_type, setup_value);
3828 /* build complete token string */
3829 sprintf(token_string, "%s%s", prefix, token_text);
3831 /* build setup entry line */
3832 line = getFormattedSetupEntry(token_string, value_string);
3834 if (token_type == TYPE_KEY_X11)
3836 Key key = *(Key *)setup_value;
3837 char *keyname = getKeyNameFromKey(key);
3839 /* add comment, if useful */
3840 if (!strEqual(keyname, "(undefined)") &&
3841 !strEqual(keyname, "(unknown)"))
3843 /* add at least one whitespace */
3845 for (i = strlen(line); i < token_comment_position; i++)
3849 strcat(line, keyname);
3856 void LoadLevelSetup_LastSeries()
3858 /* ----------------------------------------------------------------------- */
3859 /* ~/.<program>/levelsetup.conf */
3860 /* ----------------------------------------------------------------------- */
3862 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3863 SetupFileHash *level_setup_hash = NULL;
3865 /* always start with reliable default values */
3866 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3868 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3869 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3871 if (leveldir_current == NULL)
3872 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3875 if ((level_setup_hash = loadSetupFileHash(filename)))
3877 char *last_level_series =
3878 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3880 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3882 if (leveldir_current == NULL)
3883 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3885 checkSetupFileHashIdentifier(level_setup_hash, filename,
3886 getCookie("LEVELSETUP"));
3888 freeSetupFileHash(level_setup_hash);
3891 Error(ERR_WARN, "using default setup values");
3896 void SaveLevelSetup_LastSeries()
3898 /* ----------------------------------------------------------------------- */
3899 /* ~/.<program>/levelsetup.conf */
3900 /* ----------------------------------------------------------------------- */
3902 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3903 char *level_subdir = leveldir_current->subdir;
3906 InitUserDataDirectory();
3908 if (!(file = fopen(filename, MODE_WRITE)))
3910 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3915 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3916 getCookie("LEVELSETUP")));
3917 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3922 SetFilePermissions(filename, PERMS_PRIVATE);
3927 static void checkSeriesInfo()
3929 static char *level_directory = NULL;
3931 struct dirent *dir_entry;
3933 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3935 level_directory = getPath2((leveldir_current->in_user_dir ?
3936 getUserLevelDir(NULL) :
3937 options.level_directory),
3938 leveldir_current->fullpath);
3940 if ((dir = opendir(level_directory)) == NULL)
3942 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3946 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3948 if (strlen(dir_entry->d_name) > 4 &&
3949 dir_entry->d_name[3] == '.' &&
3950 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3952 char levelnum_str[4];
3955 strncpy(levelnum_str, dir_entry->d_name, 3);
3956 levelnum_str[3] = '\0';
3958 levelnum_value = atoi(levelnum_str);
3961 if (levelnum_value < leveldir_current->first_level)
3963 Error(ERR_WARN, "additional level %d found", levelnum_value);
3964 leveldir_current->first_level = levelnum_value;
3966 else if (levelnum_value > leveldir_current->last_level)
3968 Error(ERR_WARN, "additional level %d found", levelnum_value);
3969 leveldir_current->last_level = levelnum_value;
3978 void LoadLevelSetup_SeriesInfo()
3981 SetupFileHash *level_setup_hash = NULL;
3982 char *level_subdir = leveldir_current->subdir;
3984 /* always start with reliable default values */
3985 level_nr = leveldir_current->first_level;
3987 checkSeriesInfo(leveldir_current);
3989 /* ----------------------------------------------------------------------- */
3990 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3991 /* ----------------------------------------------------------------------- */
3993 level_subdir = leveldir_current->subdir;
3995 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3997 if ((level_setup_hash = loadSetupFileHash(filename)))
4001 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4005 level_nr = atoi(token_value);
4007 if (level_nr < leveldir_current->first_level)
4008 level_nr = leveldir_current->first_level;
4009 if (level_nr > leveldir_current->last_level)
4010 level_nr = leveldir_current->last_level;
4013 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4017 int level_nr = atoi(token_value);
4019 if (level_nr < leveldir_current->first_level)
4020 level_nr = leveldir_current->first_level;
4021 if (level_nr > leveldir_current->last_level + 1)
4022 level_nr = leveldir_current->last_level;
4024 if (leveldir_current->user_defined || !leveldir_current->handicap)
4025 level_nr = leveldir_current->last_level;
4027 leveldir_current->handicap_level = level_nr;
4030 checkSetupFileHashIdentifier(level_setup_hash, filename,
4031 getCookie("LEVELSETUP"));
4033 freeSetupFileHash(level_setup_hash);
4036 Error(ERR_WARN, "using default setup values");
4041 void SaveLevelSetup_SeriesInfo()
4044 char *level_subdir = leveldir_current->subdir;
4045 char *level_nr_str = int2str(level_nr, 0);
4046 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4049 /* ----------------------------------------------------------------------- */
4050 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4051 /* ----------------------------------------------------------------------- */
4053 InitLevelSetupDirectory(level_subdir);
4055 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4057 if (!(file = fopen(filename, MODE_WRITE)))
4059 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4064 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4065 getCookie("LEVELSETUP")));
4066 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4068 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4069 handicap_level_str));
4073 SetFilePermissions(filename, PERMS_PRIVATE);