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 static boolean posix_process_running_setgid()
1415 #if defined(PLATFORM_UNIX)
1416 return (getgid() != getegid());
1422 void createDirectory(char *dir, char *text, int permission_class)
1424 /* leave "other" permissions in umask untouched, but ensure group parts
1425 of USERDATA_DIR_MODE are not masked */
1426 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1427 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1428 mode_t last_umask = posix_umask(0);
1429 mode_t group_umask = ~(dir_mode & S_IRWXG);
1430 int running_setgid = posix_process_running_setgid();
1432 /* if we're setgid, protect files against "other" */
1433 /* else keep umask(0) to make the dir world-writable */
1436 posix_umask(last_umask & group_umask);
1438 dir_mode |= MODE_W_ALL;
1440 if (!fileExists(dir))
1441 if (posix_mkdir(dir, dir_mode) != 0)
1442 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1444 if (permission_class == PERMS_PUBLIC && !running_setgid)
1445 chmod(dir, dir_mode);
1447 posix_umask(last_umask); /* restore previous umask */
1450 void InitUserDataDirectory()
1452 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1455 void SetFilePermissions(char *filename, int permission_class)
1457 int running_setgid = posix_process_running_setgid();
1458 int perms = (permission_class == PERMS_PRIVATE ?
1459 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1461 if (permission_class == PERMS_PUBLIC && !running_setgid)
1462 perms |= MODE_W_ALL;
1464 chmod(filename, perms);
1467 char *getCookie(char *file_type)
1469 static char cookie[MAX_COOKIE_LEN + 1];
1471 if (strlen(program.cookie_prefix) + 1 +
1472 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1473 return "[COOKIE ERROR]"; /* should never happen */
1475 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1476 program.cookie_prefix, file_type,
1477 program.version_major, program.version_minor);
1482 int getFileVersionFromCookieString(const char *cookie)
1484 const char *ptr_cookie1, *ptr_cookie2;
1485 const char *pattern1 = "_FILE_VERSION_";
1486 const char *pattern2 = "?.?";
1487 const int len_cookie = strlen(cookie);
1488 const int len_pattern1 = strlen(pattern1);
1489 const int len_pattern2 = strlen(pattern2);
1490 const int len_pattern = len_pattern1 + len_pattern2;
1491 int version_major, version_minor;
1493 if (len_cookie <= len_pattern)
1496 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1497 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1499 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1502 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1503 ptr_cookie2[1] != '.' ||
1504 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1507 version_major = ptr_cookie2[0] - '0';
1508 version_minor = ptr_cookie2[2] - '0';
1510 return VERSION_IDENT(version_major, version_minor, 0, 0);
1513 boolean checkCookieString(const char *cookie, const char *template)
1515 const char *pattern = "_FILE_VERSION_?.?";
1516 const int len_cookie = strlen(cookie);
1517 const int len_template = strlen(template);
1518 const int len_pattern = strlen(pattern);
1520 if (len_cookie != len_template)
1523 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1529 /* ------------------------------------------------------------------------- */
1530 /* setup file list and hash handling functions */
1531 /* ------------------------------------------------------------------------- */
1533 char *getFormattedSetupEntry(char *token, char *value)
1536 static char entry[MAX_LINE_LEN];
1538 /* if value is an empty string, just return token without value */
1542 /* start with the token and some spaces to format output line */
1543 sprintf(entry, "%s:", token);
1544 for (i = strlen(entry); i < token_value_position; i++)
1547 /* continue with the token's value */
1548 strcat(entry, value);
1553 SetupFileList *newSetupFileList(char *token, char *value)
1555 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1557 new->token = getStringCopy(token);
1558 new->value = getStringCopy(value);
1565 void freeSetupFileList(SetupFileList *list)
1570 checked_free(list->token);
1571 checked_free(list->value);
1574 freeSetupFileList(list->next);
1579 char *getListEntry(SetupFileList *list, char *token)
1584 if (strEqual(list->token, token))
1587 return getListEntry(list->next, token);
1590 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1595 if (strEqual(list->token, token))
1597 checked_free(list->value);
1599 list->value = getStringCopy(value);
1603 else if (list->next == NULL)
1604 return (list->next = newSetupFileList(token, value));
1606 return setListEntry(list->next, token, value);
1609 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1614 if (list->next == NULL)
1615 return (list->next = newSetupFileList(token, value));
1617 return addListEntry(list->next, token, value);
1621 static void printSetupFileList(SetupFileList *list)
1626 printf("token: '%s'\n", list->token);
1627 printf("value: '%s'\n", list->value);
1629 printSetupFileList(list->next);
1634 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1635 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1636 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1637 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1639 #define insert_hash_entry hashtable_insert
1640 #define search_hash_entry hashtable_search
1641 #define change_hash_entry hashtable_change
1642 #define remove_hash_entry hashtable_remove
1645 unsigned int get_hash_from_key(void *key)
1650 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1651 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1652 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1653 it works better than many other constants, prime or not) has never been
1654 adequately explained.
1656 If you just want to have a good hash function, and cannot wait, djb2
1657 is one of the best string hash functions i know. It has excellent
1658 distribution and speed on many different sets of keys and table sizes.
1659 You are not likely to do better with one of the "well known" functions
1660 such as PJW, K&R, etc.
1662 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1665 char *str = (char *)key;
1666 unsigned int hash = 5381;
1669 while ((c = *str++))
1670 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1675 static int keys_are_equal(void *key1, void *key2)
1677 return (strEqual((char *)key1, (char *)key2));
1680 SetupFileHash *newSetupFileHash()
1682 SetupFileHash *new_hash =
1683 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1685 if (new_hash == NULL)
1686 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1691 void freeSetupFileHash(SetupFileHash *hash)
1696 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1699 char *getHashEntry(SetupFileHash *hash, char *token)
1704 return search_hash_entry(hash, token);
1707 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1714 value_copy = getStringCopy(value);
1716 /* change value; if it does not exist, insert it as new */
1717 if (!change_hash_entry(hash, token, value_copy))
1718 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1719 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1722 char *removeHashEntry(SetupFileHash *hash, char *token)
1727 return remove_hash_entry(hash, token);
1731 static void printSetupFileHash(SetupFileHash *hash)
1733 BEGIN_HASH_ITERATION(hash, itr)
1735 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1736 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1738 END_HASH_ITERATION(hash, itr)
1742 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1743 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1744 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1746 static boolean token_value_separator_found = FALSE;
1747 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1748 static boolean token_value_separator_warning = FALSE;
1750 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1751 static boolean token_already_exists_warning = FALSE;
1754 static boolean getTokenValueFromSetupLineExt(char *line,
1755 char **token_ptr, char **value_ptr,
1756 char *filename, char *line_raw,
1758 boolean separator_required)
1760 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1761 char *token, *value, *line_ptr;
1763 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1764 if (line_raw == NULL)
1766 strncpy(line_copy, line, MAX_LINE_LEN);
1767 line_copy[MAX_LINE_LEN] = '\0';
1770 strcpy(line_raw_copy, line_copy);
1771 line_raw = line_raw_copy;
1774 /* cut trailing comment from input line */
1775 for (line_ptr = line; *line_ptr; line_ptr++)
1777 if (*line_ptr == '#')
1784 /* cut trailing whitespaces from input line */
1785 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1786 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1789 /* ignore empty lines */
1793 /* cut leading whitespaces from token */
1794 for (token = line; *token; token++)
1795 if (*token != ' ' && *token != '\t')
1798 /* start with empty value as reliable default */
1801 token_value_separator_found = FALSE;
1803 /* find end of token to determine start of value */
1804 for (line_ptr = token; *line_ptr; line_ptr++)
1807 /* first look for an explicit token/value separator, like ':' or '=' */
1808 if (*line_ptr == ':' || *line_ptr == '=')
1810 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1813 *line_ptr = '\0'; /* terminate token string */
1814 value = line_ptr + 1; /* set beginning of value */
1816 token_value_separator_found = TRUE;
1822 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1823 /* fallback: if no token/value separator found, also allow whitespaces */
1824 if (!token_value_separator_found && !separator_required)
1826 for (line_ptr = token; *line_ptr; line_ptr++)
1828 if (*line_ptr == ' ' || *line_ptr == '\t')
1830 *line_ptr = '\0'; /* terminate token string */
1831 value = line_ptr + 1; /* set beginning of value */
1833 token_value_separator_found = TRUE;
1839 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1840 if (token_value_separator_found)
1842 if (!token_value_separator_warning)
1844 Error(ERR_INFO_LINE, "-");
1846 if (filename != NULL)
1848 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1849 Error(ERR_INFO, "- config file: '%s'", filename);
1853 Error(ERR_WARN, "missing token/value separator(s):");
1856 token_value_separator_warning = TRUE;
1859 if (filename != NULL)
1860 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1862 Error(ERR_INFO, "- line: '%s'", line_raw);
1868 /* cut trailing whitespaces from token */
1869 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1870 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1873 /* cut leading whitespaces from value */
1874 for (; *value; value++)
1875 if (*value != ' ' && *value != '\t')
1880 value = "true"; /* treat tokens without value as "true" */
1889 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1891 /* while the internal (old) interface does not require a token/value
1892 separator (for downwards compatibility with existing files which
1893 don't use them), it is mandatory for the external (new) interface */
1895 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1899 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1900 boolean top_recursion_level, boolean is_hash)
1902 static SetupFileHash *include_filename_hash = NULL;
1903 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1904 char *token, *value, *line_ptr;
1905 void *insert_ptr = NULL;
1906 boolean read_continued_line = FALSE;
1908 int line_nr = 0, token_count = 0, include_count = 0;
1910 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1911 token_value_separator_warning = FALSE;
1914 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1915 token_already_exists_warning = FALSE;
1918 if (!(file = fopen(filename, MODE_READ)))
1920 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1925 /* use "insert pointer" to store list end for constant insertion complexity */
1927 insert_ptr = setup_file_data;
1929 /* on top invocation, create hash to mark included files (to prevent loops) */
1930 if (top_recursion_level)
1931 include_filename_hash = newSetupFileHash();
1933 /* mark this file as already included (to prevent including it again) */
1934 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1938 /* read next line of input file */
1939 if (!fgets(line, MAX_LINE_LEN, file))
1942 /* check if line was completely read and is terminated by line break */
1943 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1946 /* cut trailing line break (this can be newline and/or carriage return) */
1947 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1948 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1951 /* copy raw input line for later use (mainly debugging output) */
1952 strcpy(line_raw, line);
1954 if (read_continued_line)
1957 /* !!! ??? WHY ??? !!! */
1958 /* cut leading whitespaces from input line */
1959 for (line_ptr = line; *line_ptr; line_ptr++)
1960 if (*line_ptr != ' ' && *line_ptr != '\t')
1964 /* append new line to existing line, if there is enough space */
1965 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1966 strcat(previous_line, line_ptr);
1968 strcpy(line, previous_line); /* copy storage buffer to line */
1970 read_continued_line = FALSE;
1973 /* if the last character is '\', continue at next line */
1974 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1976 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1977 strcpy(previous_line, line); /* copy line to storage buffer */
1979 read_continued_line = TRUE;
1984 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1985 line_raw, line_nr, FALSE))
1990 if (strEqual(token, "include"))
1992 if (getHashEntry(include_filename_hash, value) == NULL)
1994 char *basepath = getBasePath(filename);
1995 char *basename = getBaseName(value);
1996 char *filename_include = getPath2(basepath, basename);
1999 Error(ERR_INFO, "[including file '%s']", filename_include);
2002 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2006 free(filename_include);
2012 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2019 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2021 getHashEntry((SetupFileHash *)setup_file_data, token);
2023 if (old_value != NULL)
2025 if (!token_already_exists_warning)
2027 Error(ERR_INFO_LINE, "-");
2028 Error(ERR_WARN, "duplicate token(s) found in config file:");
2029 Error(ERR_INFO, "- config file: '%s'", filename);
2031 token_already_exists_warning = TRUE;
2034 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2035 Error(ERR_INFO, " old value: '%s'", old_value);
2036 Error(ERR_INFO, " new value: '%s'", value);
2040 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2044 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2054 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2055 if (token_value_separator_warning)
2056 Error(ERR_INFO_LINE, "-");
2059 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2060 if (token_already_exists_warning)
2061 Error(ERR_INFO_LINE, "-");
2064 if (token_count == 0 && include_count == 0)
2065 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2067 if (top_recursion_level)
2068 freeSetupFileHash(include_filename_hash);
2075 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2076 boolean top_recursion_level, boolean is_hash)
2078 static SetupFileHash *include_filename_hash = NULL;
2079 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2080 char *token, *value, *line_ptr;
2081 void *insert_ptr = NULL;
2082 boolean read_continued_line = FALSE;
2085 int token_count = 0;
2087 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2088 token_value_separator_warning = FALSE;
2091 if (!(file = fopen(filename, MODE_READ)))
2093 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2098 /* use "insert pointer" to store list end for constant insertion complexity */
2100 insert_ptr = setup_file_data;
2102 /* on top invocation, create hash to mark included files (to prevent loops) */
2103 if (top_recursion_level)
2104 include_filename_hash = newSetupFileHash();
2106 /* mark this file as already included (to prevent including it again) */
2107 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2111 /* read next line of input file */
2112 if (!fgets(line, MAX_LINE_LEN, file))
2115 /* check if line was completely read and is terminated by line break */
2116 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2119 /* cut trailing line break (this can be newline and/or carriage return) */
2120 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2121 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2124 /* copy raw input line for later use (mainly debugging output) */
2125 strcpy(line_raw, line);
2127 if (read_continued_line)
2129 /* cut leading whitespaces from input line */
2130 for (line_ptr = line; *line_ptr; line_ptr++)
2131 if (*line_ptr != ' ' && *line_ptr != '\t')
2134 /* append new line to existing line, if there is enough space */
2135 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2136 strcat(previous_line, line_ptr);
2138 strcpy(line, previous_line); /* copy storage buffer to line */
2140 read_continued_line = FALSE;
2143 /* if the last character is '\', continue at next line */
2144 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2146 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2147 strcpy(previous_line, line); /* copy line to storage buffer */
2149 read_continued_line = TRUE;
2154 /* cut trailing comment from input line */
2155 for (line_ptr = line; *line_ptr; line_ptr++)
2157 if (*line_ptr == '#')
2164 /* cut trailing whitespaces from input line */
2165 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2166 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2169 /* ignore empty lines */
2173 /* cut leading whitespaces from token */
2174 for (token = line; *token; token++)
2175 if (*token != ' ' && *token != '\t')
2178 /* start with empty value as reliable default */
2181 token_value_separator_found = FALSE;
2183 /* find end of token to determine start of value */
2184 for (line_ptr = token; *line_ptr; line_ptr++)
2187 /* first look for an explicit token/value separator, like ':' or '=' */
2188 if (*line_ptr == ':' || *line_ptr == '=')
2190 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2193 *line_ptr = '\0'; /* terminate token string */
2194 value = line_ptr + 1; /* set beginning of value */
2196 token_value_separator_found = TRUE;
2202 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2203 /* fallback: if no token/value separator found, also allow whitespaces */
2204 if (!token_value_separator_found)
2206 for (line_ptr = token; *line_ptr; line_ptr++)
2208 if (*line_ptr == ' ' || *line_ptr == '\t')
2210 *line_ptr = '\0'; /* terminate token string */
2211 value = line_ptr + 1; /* set beginning of value */
2213 token_value_separator_found = TRUE;
2219 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2220 if (token_value_separator_found)
2222 if (!token_value_separator_warning)
2224 Error(ERR_INFO_LINE, "-");
2225 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2226 Error(ERR_INFO, "- config file: '%s'", filename);
2228 token_value_separator_warning = TRUE;
2231 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2237 /* cut trailing whitespaces from token */
2238 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2239 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2242 /* cut leading whitespaces from value */
2243 for (; *value; value++)
2244 if (*value != ' ' && *value != '\t')
2249 value = "true"; /* treat tokens without value as "true" */
2254 if (strEqual(token, "include"))
2256 if (getHashEntry(include_filename_hash, value) == NULL)
2258 char *basepath = getBasePath(filename);
2259 char *basename = getBaseName(value);
2260 char *filename_include = getPath2(basepath, basename);
2263 Error(ERR_INFO, "[including file '%s']", filename_include);
2266 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2270 free(filename_include);
2274 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2280 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2282 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2291 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2292 if (token_value_separator_warning)
2293 Error(ERR_INFO_LINE, "-");
2296 if (token_count == 0)
2297 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2299 if (top_recursion_level)
2300 freeSetupFileHash(include_filename_hash);
2306 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2310 if (!(file = fopen(filename, MODE_WRITE)))
2312 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2317 BEGIN_HASH_ITERATION(hash, itr)
2319 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2320 HASH_ITERATION_VALUE(itr)));
2322 END_HASH_ITERATION(hash, itr)
2327 SetupFileList *loadSetupFileList(char *filename)
2329 SetupFileList *setup_file_list = newSetupFileList("", "");
2330 SetupFileList *first_valid_list_entry;
2332 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2334 freeSetupFileList(setup_file_list);
2339 first_valid_list_entry = setup_file_list->next;
2341 /* free empty list header */
2342 setup_file_list->next = NULL;
2343 freeSetupFileList(setup_file_list);
2345 return first_valid_list_entry;
2348 SetupFileHash *loadSetupFileHash(char *filename)
2350 SetupFileHash *setup_file_hash = newSetupFileHash();
2352 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2354 freeSetupFileHash(setup_file_hash);
2359 return setup_file_hash;
2362 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2363 char *filename, char *identifier)
2365 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2368 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2369 else if (!checkCookieString(value, identifier))
2370 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2374 /* ========================================================================= */
2375 /* setup file stuff */
2376 /* ========================================================================= */
2378 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2379 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2380 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2382 /* level directory info */
2383 #define LEVELINFO_TOKEN_IDENTIFIER 0
2384 #define LEVELINFO_TOKEN_NAME 1
2385 #define LEVELINFO_TOKEN_NAME_SORTING 2
2386 #define LEVELINFO_TOKEN_AUTHOR 3
2387 #define LEVELINFO_TOKEN_YEAR 4
2388 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2389 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2390 #define LEVELINFO_TOKEN_TESTED_BY 7
2391 #define LEVELINFO_TOKEN_LEVELS 8
2392 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2393 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2394 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2395 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2396 #define LEVELINFO_TOKEN_READONLY 13
2397 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2398 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2399 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2400 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2401 #define LEVELINFO_TOKEN_MUSIC_SET 18
2402 #define LEVELINFO_TOKEN_FILENAME 19
2403 #define LEVELINFO_TOKEN_FILETYPE 20
2404 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2405 #define LEVELINFO_TOKEN_HANDICAP 22
2406 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2408 #define NUM_LEVELINFO_TOKENS 24
2410 static LevelDirTree ldi;
2412 static struct TokenInfo levelinfo_tokens[] =
2414 /* level directory info */
2415 { TYPE_STRING, &ldi.identifier, "identifier" },
2416 { TYPE_STRING, &ldi.name, "name" },
2417 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2418 { TYPE_STRING, &ldi.author, "author" },
2419 { TYPE_STRING, &ldi.year, "year" },
2420 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2421 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2422 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2423 { TYPE_INTEGER, &ldi.levels, "levels" },
2424 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2425 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2426 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2427 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2428 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2429 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2430 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2431 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2432 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2433 { TYPE_STRING, &ldi.music_set, "music_set" },
2434 { TYPE_STRING, &ldi.level_filename, "filename" },
2435 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2436 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2437 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2438 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2441 static struct TokenInfo artworkinfo_tokens[] =
2443 /* artwork directory info */
2444 { TYPE_STRING, &ldi.identifier, "identifier" },
2445 { TYPE_STRING, &ldi.subdir, "subdir" },
2446 { TYPE_STRING, &ldi.name, "name" },
2447 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2448 { TYPE_STRING, &ldi.author, "author" },
2449 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2450 { TYPE_STRING, &ldi.basepath, "basepath" },
2451 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2452 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2453 { TYPE_INTEGER, &ldi.color, "color" },
2454 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2459 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2463 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2464 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2465 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2466 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2469 ti->node_parent = NULL;
2470 ti->node_group = NULL;
2477 ti->fullpath = NULL;
2478 ti->basepath = NULL;
2479 ti->identifier = NULL;
2480 ti->name = getStringCopy(ANONYMOUS_NAME);
2481 ti->name_sorting = NULL;
2482 ti->author = getStringCopy(ANONYMOUS_NAME);
2485 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2486 ti->latest_engine = FALSE; /* default: get from level */
2487 ti->parent_link = FALSE;
2488 ti->in_user_dir = FALSE;
2489 ti->user_defined = FALSE;
2491 ti->class_desc = NULL;
2493 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2495 if (ti->type == TREE_TYPE_LEVEL_DIR)
2497 ti->imported_from = NULL;
2498 ti->imported_by = NULL;
2499 ti->tested_by = NULL;
2501 ti->graphics_set_ecs = NULL;
2502 ti->graphics_set_aga = NULL;
2503 ti->graphics_set = NULL;
2504 ti->sounds_set = NULL;
2505 ti->music_set = NULL;
2506 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2507 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2508 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2510 ti->level_filename = NULL;
2511 ti->level_filetype = NULL;
2513 ti->special_flags = NULL;
2516 ti->first_level = 0;
2518 ti->level_group = FALSE;
2519 ti->handicap_level = 0;
2520 ti->readonly = TRUE;
2521 ti->handicap = TRUE;
2522 ti->skip_levels = FALSE;
2526 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2530 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2532 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2537 /* copy all values from the parent structure */
2539 ti->type = parent->type;
2541 ti->node_top = parent->node_top;
2542 ti->node_parent = parent;
2543 ti->node_group = NULL;
2550 ti->fullpath = NULL;
2551 ti->basepath = NULL;
2552 ti->identifier = NULL;
2553 ti->name = getStringCopy(ANONYMOUS_NAME);
2554 ti->name_sorting = NULL;
2555 ti->author = getStringCopy(parent->author);
2556 ti->year = getStringCopy(parent->year);
2558 ti->sort_priority = parent->sort_priority;
2559 ti->latest_engine = parent->latest_engine;
2560 ti->parent_link = FALSE;
2561 ti->in_user_dir = parent->in_user_dir;
2562 ti->user_defined = parent->user_defined;
2563 ti->color = parent->color;
2564 ti->class_desc = getStringCopy(parent->class_desc);
2566 ti->infotext = getStringCopy(parent->infotext);
2568 if (ti->type == TREE_TYPE_LEVEL_DIR)
2570 ti->imported_from = getStringCopy(parent->imported_from);
2571 ti->imported_by = getStringCopy(parent->imported_by);
2572 ti->tested_by = getStringCopy(parent->tested_by);
2574 ti->graphics_set_ecs = NULL;
2575 ti->graphics_set_aga = NULL;
2576 ti->graphics_set = NULL;
2577 ti->sounds_set = NULL;
2578 ti->music_set = NULL;
2579 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2580 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2581 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2583 ti->level_filename = NULL;
2584 ti->level_filetype = NULL;
2586 ti->special_flags = getStringCopy(parent->special_flags);
2589 ti->first_level = 0;
2591 ti->level_group = FALSE;
2592 ti->handicap_level = 0;
2593 ti->readonly = TRUE;
2594 ti->handicap = TRUE;
2595 ti->skip_levels = FALSE;
2599 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2601 TreeInfo *ti_copy = newTreeInfo();
2603 /* copy all values from the original structure */
2605 ti_copy->type = ti->type;
2607 ti_copy->node_top = ti->node_top;
2608 ti_copy->node_parent = ti->node_parent;
2609 ti_copy->node_group = ti->node_group;
2610 ti_copy->next = ti->next;
2612 ti_copy->cl_first = ti->cl_first;
2613 ti_copy->cl_cursor = ti->cl_cursor;
2615 ti_copy->subdir = getStringCopy(ti->subdir);
2616 ti_copy->fullpath = getStringCopy(ti->fullpath);
2617 ti_copy->basepath = getStringCopy(ti->basepath);
2618 ti_copy->identifier = getStringCopy(ti->identifier);
2619 ti_copy->name = getStringCopy(ti->name);
2620 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2621 ti_copy->author = getStringCopy(ti->author);
2622 ti_copy->year = getStringCopy(ti->year);
2623 ti_copy->imported_from = getStringCopy(ti->imported_from);
2624 ti_copy->imported_by = getStringCopy(ti->imported_by);
2625 ti_copy->tested_by = getStringCopy(ti->tested_by);
2627 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2628 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2629 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2630 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2631 ti_copy->music_set = getStringCopy(ti->music_set);
2632 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2633 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2634 ti_copy->music_path = getStringCopy(ti->music_path);
2636 ti_copy->level_filename = getStringCopy(ti->level_filename);
2637 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2639 ti_copy->special_flags = getStringCopy(ti->special_flags);
2641 ti_copy->levels = ti->levels;
2642 ti_copy->first_level = ti->first_level;
2643 ti_copy->last_level = ti->last_level;
2644 ti_copy->sort_priority = ti->sort_priority;
2646 ti_copy->latest_engine = ti->latest_engine;
2648 ti_copy->level_group = ti->level_group;
2649 ti_copy->parent_link = ti->parent_link;
2650 ti_copy->in_user_dir = ti->in_user_dir;
2651 ti_copy->user_defined = ti->user_defined;
2652 ti_copy->readonly = ti->readonly;
2653 ti_copy->handicap = ti->handicap;
2654 ti_copy->skip_levels = ti->skip_levels;
2656 ti_copy->color = ti->color;
2657 ti_copy->class_desc = getStringCopy(ti->class_desc);
2658 ti_copy->handicap_level = ti->handicap_level;
2660 ti_copy->infotext = getStringCopy(ti->infotext);
2665 static void freeTreeInfo(TreeInfo *ti)
2670 checked_free(ti->subdir);
2671 checked_free(ti->fullpath);
2672 checked_free(ti->basepath);
2673 checked_free(ti->identifier);
2675 checked_free(ti->name);
2676 checked_free(ti->name_sorting);
2677 checked_free(ti->author);
2678 checked_free(ti->year);
2680 checked_free(ti->class_desc);
2682 checked_free(ti->infotext);
2684 if (ti->type == TREE_TYPE_LEVEL_DIR)
2686 checked_free(ti->imported_from);
2687 checked_free(ti->imported_by);
2688 checked_free(ti->tested_by);
2690 checked_free(ti->graphics_set_ecs);
2691 checked_free(ti->graphics_set_aga);
2692 checked_free(ti->graphics_set);
2693 checked_free(ti->sounds_set);
2694 checked_free(ti->music_set);
2696 checked_free(ti->graphics_path);
2697 checked_free(ti->sounds_path);
2698 checked_free(ti->music_path);
2700 checked_free(ti->level_filename);
2701 checked_free(ti->level_filetype);
2703 checked_free(ti->special_flags);
2709 void setSetupInfo(struct TokenInfo *token_info,
2710 int token_nr, char *token_value)
2712 int token_type = token_info[token_nr].type;
2713 void *setup_value = token_info[token_nr].value;
2715 if (token_value == NULL)
2718 /* set setup field to corresponding token value */
2723 *(boolean *)setup_value = get_boolean_from_string(token_value);
2727 *(int *)setup_value = get_switch3_from_string(token_value);
2731 *(Key *)setup_value = getKeyFromKeyName(token_value);
2735 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2739 *(int *)setup_value = get_integer_from_string(token_value);
2743 checked_free(*(char **)setup_value);
2744 *(char **)setup_value = getStringCopy(token_value);
2752 static int compareTreeInfoEntries(const void *object1, const void *object2)
2754 const TreeInfo *entry1 = *((TreeInfo **)object1);
2755 const TreeInfo *entry2 = *((TreeInfo **)object2);
2756 int class_sorting1, class_sorting2;
2759 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2761 class_sorting1 = LEVELSORTING(entry1);
2762 class_sorting2 = LEVELSORTING(entry2);
2766 class_sorting1 = ARTWORKSORTING(entry1);
2767 class_sorting2 = ARTWORKSORTING(entry2);
2770 if (entry1->parent_link || entry2->parent_link)
2771 compare_result = (entry1->parent_link ? -1 : +1);
2772 else if (entry1->sort_priority == entry2->sort_priority)
2774 char *name1 = getStringToLower(entry1->name_sorting);
2775 char *name2 = getStringToLower(entry2->name_sorting);
2777 compare_result = strcmp(name1, name2);
2782 else if (class_sorting1 == class_sorting2)
2783 compare_result = entry1->sort_priority - entry2->sort_priority;
2785 compare_result = class_sorting1 - class_sorting2;
2787 return compare_result;
2790 static void createParentTreeInfoNode(TreeInfo *node_parent)
2794 if (node_parent == NULL)
2797 ti_new = newTreeInfo();
2798 setTreeInfoToDefaults(ti_new, node_parent->type);
2800 ti_new->node_parent = node_parent;
2801 ti_new->parent_link = TRUE;
2803 setString(&ti_new->identifier, node_parent->identifier);
2804 setString(&ti_new->name, ".. (parent directory)");
2805 setString(&ti_new->name_sorting, ti_new->name);
2807 setString(&ti_new->subdir, "..");
2808 setString(&ti_new->fullpath, node_parent->fullpath);
2810 ti_new->sort_priority = node_parent->sort_priority;
2811 ti_new->latest_engine = node_parent->latest_engine;
2813 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2815 pushTreeInfo(&node_parent->node_group, ti_new);
2819 /* -------------------------------------------------------------------------- */
2820 /* functions for handling level and custom artwork info cache */
2821 /* -------------------------------------------------------------------------- */
2823 static void LoadArtworkInfoCache()
2825 InitCacheDirectory();
2827 if (artworkinfo_cache_old == NULL)
2829 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2831 /* try to load artwork info hash from already existing cache file */
2832 artworkinfo_cache_old = loadSetupFileHash(filename);
2834 /* if no artwork info cache file was found, start with empty hash */
2835 if (artworkinfo_cache_old == NULL)
2836 artworkinfo_cache_old = newSetupFileHash();
2841 if (artworkinfo_cache_new == NULL)
2842 artworkinfo_cache_new = newSetupFileHash();
2845 static void SaveArtworkInfoCache()
2847 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2849 InitCacheDirectory();
2851 saveSetupFileHash(artworkinfo_cache_new, filename);
2856 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2858 static char *prefix = NULL;
2860 checked_free(prefix);
2862 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2867 /* (identical to above function, but separate string buffer needed -- nasty) */
2868 static char *getCacheToken(char *prefix, char *suffix)
2870 static char *token = NULL;
2872 checked_free(token);
2874 token = getStringCat2WithSeparator(prefix, suffix, ".");
2879 static char *getFileTimestampString(char *filename)
2882 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2884 struct stat file_status;
2886 if (stat(filename, &file_status) != 0) /* cannot stat file */
2887 return getStringCopy(i_to_a(0));
2889 return getStringCopy(i_to_a(file_status.st_mtime));
2893 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2895 struct stat file_status;
2897 if (timestamp_string == NULL)
2900 if (stat(filename, &file_status) != 0) /* cannot stat file */
2903 return (file_status.st_mtime != atoi(timestamp_string));
2906 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2908 char *identifier = level_node->subdir;
2909 char *type_string = ARTWORK_DIRECTORY(type);
2910 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2911 char *token_main = getCacheToken(token_prefix, "CACHED");
2912 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2913 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2914 TreeInfo *artwork_info = NULL;
2916 if (!use_artworkinfo_cache)
2923 artwork_info = newTreeInfo();
2924 setTreeInfoToDefaults(artwork_info, type);
2926 /* set all structure fields according to the token/value pairs */
2927 ldi = *artwork_info;
2928 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2930 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2931 char *value = getHashEntry(artworkinfo_cache_old, token);
2933 setSetupInfo(artworkinfo_tokens, i, value);
2935 /* check if cache entry for this item is invalid or incomplete */
2939 Error(ERR_WARN, "cache entry '%s' invalid", token);
2946 *artwork_info = ldi;
2951 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2952 LEVELINFO_FILENAME);
2953 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2954 ARTWORKINFO_FILENAME(type));
2956 /* check if corresponding "levelinfo.conf" file has changed */
2957 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2958 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2960 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2963 /* check if corresponding "<artworkinfo>.conf" file has changed */
2964 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2965 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2967 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2972 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2975 checked_free(filename_levelinfo);
2976 checked_free(filename_artworkinfo);
2979 if (!cached && artwork_info != NULL)
2981 freeTreeInfo(artwork_info);
2986 return artwork_info;
2989 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2990 LevelDirTree *level_node, int type)
2992 char *identifier = level_node->subdir;
2993 char *type_string = ARTWORK_DIRECTORY(type);
2994 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2995 char *token_main = getCacheToken(token_prefix, "CACHED");
2996 boolean set_cache_timestamps = TRUE;
2999 setHashEntry(artworkinfo_cache_new, token_main, "true");
3001 if (set_cache_timestamps)
3003 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3004 LEVELINFO_FILENAME);
3005 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3006 ARTWORKINFO_FILENAME(type));
3007 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3008 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3010 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3011 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3013 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3014 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3016 checked_free(filename_levelinfo);
3017 checked_free(filename_artworkinfo);
3018 checked_free(timestamp_levelinfo);
3019 checked_free(timestamp_artworkinfo);
3022 ldi = *artwork_info;
3023 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3025 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3026 char *value = getSetupValue(artworkinfo_tokens[i].type,
3027 artworkinfo_tokens[i].value);
3029 setHashEntry(artworkinfo_cache_new, token, value);
3034 /* -------------------------------------------------------------------------- */
3035 /* functions for loading level info and custom artwork info */
3036 /* -------------------------------------------------------------------------- */
3038 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3039 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3041 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3042 TreeInfo *node_parent,
3043 char *level_directory,
3044 char *directory_name)
3047 static unsigned long progress_delay = 0;
3048 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3050 char *directory_path = getPath2(level_directory, directory_name);
3051 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3052 SetupFileHash *setup_file_hash;
3053 LevelDirTree *leveldir_new = NULL;
3056 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3057 if (!options.debug && !fileExists(filename))
3059 free(directory_path);
3065 setup_file_hash = loadSetupFileHash(filename);
3067 if (setup_file_hash == NULL)
3069 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3071 free(directory_path);
3077 leveldir_new = newTreeInfo();
3080 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3082 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3084 leveldir_new->subdir = getStringCopy(directory_name);
3086 checkSetupFileHashIdentifier(setup_file_hash, filename,
3087 getCookie("LEVELINFO"));
3089 /* set all structure fields according to the token/value pairs */
3090 ldi = *leveldir_new;
3091 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3092 setSetupInfo(levelinfo_tokens, i,
3093 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3094 *leveldir_new = ldi;
3096 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3097 setString(&leveldir_new->name, leveldir_new->subdir);
3099 if (leveldir_new->identifier == NULL)
3100 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3102 if (leveldir_new->name_sorting == NULL)
3103 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3105 if (node_parent == NULL) /* top level group */
3107 leveldir_new->basepath = getStringCopy(level_directory);
3108 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3110 else /* sub level group */
3112 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3113 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3117 if (leveldir_new->levels < 1)
3118 leveldir_new->levels = 1;
3121 leveldir_new->last_level =
3122 leveldir_new->first_level + leveldir_new->levels - 1;
3124 leveldir_new->in_user_dir =
3125 (!strEqual(leveldir_new->basepath, options.level_directory));
3127 /* adjust some settings if user's private level directory was detected */
3128 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3129 leveldir_new->in_user_dir &&
3130 (strEqual(leveldir_new->subdir, getLoginName()) ||
3131 strEqual(leveldir_new->name, getLoginName()) ||
3132 strEqual(leveldir_new->author, getRealName())))
3134 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3135 leveldir_new->readonly = FALSE;
3138 leveldir_new->user_defined =
3139 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3141 leveldir_new->color = LEVELCOLOR(leveldir_new);
3143 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3145 leveldir_new->handicap_level = /* set handicap to default value */
3146 (leveldir_new->user_defined || !leveldir_new->handicap ?
3147 leveldir_new->last_level : leveldir_new->first_level);
3151 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3152 leveldir_new->level_group);
3154 if (leveldir_new->level_group ||
3155 DelayReached(&progress_delay, progress_delay_value))
3156 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3159 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3163 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3165 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3167 /* skip level sets without levels (which are probably artwork base sets) */
3169 freeSetupFileHash(setup_file_hash);
3170 free(directory_path);
3178 pushTreeInfo(node_first, leveldir_new);
3180 freeSetupFileHash(setup_file_hash);
3182 if (leveldir_new->level_group)
3184 /* create node to link back to current level directory */
3185 createParentTreeInfoNode(leveldir_new);
3187 /* recursively step into sub-directory and look for more level series */
3188 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3189 leveldir_new, directory_path);
3192 free(directory_path);
3198 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3199 TreeInfo *node_parent,
3200 char *level_directory)
3203 struct dirent *dir_entry;
3204 boolean valid_entry_found = FALSE;
3206 if ((dir = opendir(level_directory)) == NULL)
3208 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3212 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3214 struct stat file_status;
3215 char *directory_name = dir_entry->d_name;
3216 char *directory_path = getPath2(level_directory, directory_name);
3218 /* skip entries for current and parent directory */
3219 if (strEqual(directory_name, ".") ||
3220 strEqual(directory_name, ".."))
3222 free(directory_path);
3226 /* find out if directory entry is itself a directory */
3227 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3228 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3230 free(directory_path);
3234 free(directory_path);
3236 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3237 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3238 strEqual(directory_name, MUSIC_DIRECTORY))
3241 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3248 /* special case: top level directory may directly contain "levelinfo.conf" */
3249 if (node_parent == NULL && !valid_entry_found)
3251 /* check if this directory directly contains a file "levelinfo.conf" */
3252 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3253 level_directory, ".");
3256 if (!valid_entry_found)
3257 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3261 boolean AdjustGraphicsForEMC()
3263 boolean settings_changed = FALSE;
3265 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3266 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3268 return settings_changed;
3271 void LoadLevelInfo()
3273 InitUserLevelDirectory(getLoginName());
3275 DrawInitText("Loading level series", 120, FC_GREEN);
3277 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3278 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3280 /* after loading all level set information, clone the level directory tree
3281 and remove all level sets without levels (these may still contain artwork
3282 to be offered in the setup menu as "custom artwork", and are therefore
3283 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3284 leveldir_first_all = leveldir_first;
3285 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3287 AdjustGraphicsForEMC();
3289 /* before sorting, the first entries will be from the user directory */
3290 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3292 if (leveldir_first == NULL)
3293 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3295 sortTreeInfo(&leveldir_first);
3298 dumpTreeInfo(leveldir_first, 0);
3302 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3303 TreeInfo *node_parent,
3304 char *base_directory,
3305 char *directory_name, int type)
3307 char *directory_path = getPath2(base_directory, directory_name);
3308 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3309 SetupFileHash *setup_file_hash = NULL;
3310 TreeInfo *artwork_new = NULL;
3313 if (fileExists(filename))
3314 setup_file_hash = loadSetupFileHash(filename);
3316 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3319 struct dirent *dir_entry;
3320 boolean valid_file_found = FALSE;
3322 if ((dir = opendir(directory_path)) != NULL)
3324 while ((dir_entry = readdir(dir)) != NULL)
3326 char *entry_name = dir_entry->d_name;
3328 if (FileIsArtworkType(entry_name, type))
3330 valid_file_found = TRUE;
3338 if (!valid_file_found)
3340 if (!strEqual(directory_name, "."))
3341 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3343 free(directory_path);
3350 artwork_new = newTreeInfo();
3353 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3355 setTreeInfoToDefaults(artwork_new, type);
3357 artwork_new->subdir = getStringCopy(directory_name);
3359 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3362 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3365 /* set all structure fields according to the token/value pairs */
3367 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3368 setSetupInfo(levelinfo_tokens, i,
3369 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3372 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3373 setString(&artwork_new->name, artwork_new->subdir);
3375 if (artwork_new->identifier == NULL)
3376 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3378 if (artwork_new->name_sorting == NULL)
3379 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3382 if (node_parent == NULL) /* top level group */
3384 artwork_new->basepath = getStringCopy(base_directory);
3385 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3387 else /* sub level group */
3389 artwork_new->basepath = getStringCopy(node_parent->basepath);
3390 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3393 artwork_new->in_user_dir =
3394 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3396 /* (may use ".sort_priority" from "setup_file_hash" above) */
3397 artwork_new->color = ARTWORKCOLOR(artwork_new);
3399 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3401 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3403 if (strEqual(artwork_new->subdir, "."))
3405 if (artwork_new->user_defined)
3407 setString(&artwork_new->identifier, "private");
3408 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3412 setString(&artwork_new->identifier, "classic");
3413 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3416 /* set to new values after changing ".sort_priority" */
3417 artwork_new->color = ARTWORKCOLOR(artwork_new);
3419 setString(&artwork_new->class_desc,
3420 getLevelClassDescription(artwork_new));
3424 setString(&artwork_new->identifier, artwork_new->subdir);
3427 setString(&artwork_new->name, artwork_new->identifier);
3428 setString(&artwork_new->name_sorting, artwork_new->name);
3432 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3435 pushTreeInfo(node_first, artwork_new);
3437 freeSetupFileHash(setup_file_hash);
3439 free(directory_path);
3445 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3446 TreeInfo *node_parent,
3447 char *base_directory, int type)
3450 struct dirent *dir_entry;
3451 boolean valid_entry_found = FALSE;
3453 if ((dir = opendir(base_directory)) == NULL)
3455 /* display error if directory is main "options.graphics_directory" etc. */
3456 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3457 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3462 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3464 struct stat file_status;
3465 char *directory_name = dir_entry->d_name;
3466 char *directory_path = getPath2(base_directory, directory_name);
3468 /* skip directory entries for current and parent directory */
3469 if (strEqual(directory_name, ".") ||
3470 strEqual(directory_name, ".."))
3472 free(directory_path);
3476 /* skip directory entries which are not a directory or are not accessible */
3477 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3478 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3480 free(directory_path);
3484 free(directory_path);
3486 /* check if this directory contains artwork with or without config file */
3487 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3489 directory_name, type);
3494 /* check if this directory directly contains artwork itself */
3495 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3496 base_directory, ".",
3498 if (!valid_entry_found)
3499 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3503 static TreeInfo *getDummyArtworkInfo(int type)
3505 /* this is only needed when there is completely no artwork available */
3506 TreeInfo *artwork_new = newTreeInfo();
3508 setTreeInfoToDefaults(artwork_new, type);
3510 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3511 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3512 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3514 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3515 setString(&artwork_new->name, UNDEFINED_FILENAME);
3516 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3521 void LoadArtworkInfo()
3523 LoadArtworkInfoCache();
3525 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3527 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3528 options.graphics_directory,
3529 TREE_TYPE_GRAPHICS_DIR);
3530 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3531 getUserGraphicsDir(),
3532 TREE_TYPE_GRAPHICS_DIR);
3534 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3535 options.sounds_directory,
3536 TREE_TYPE_SOUNDS_DIR);
3537 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3539 TREE_TYPE_SOUNDS_DIR);
3541 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3542 options.music_directory,
3543 TREE_TYPE_MUSIC_DIR);
3544 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3546 TREE_TYPE_MUSIC_DIR);
3548 if (artwork.gfx_first == NULL)
3549 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3550 if (artwork.snd_first == NULL)
3551 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3552 if (artwork.mus_first == NULL)
3553 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3555 /* before sorting, the first entries will be from the user directory */
3556 artwork.gfx_current =
3557 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3558 if (artwork.gfx_current == NULL)
3559 artwork.gfx_current =
3560 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3561 if (artwork.gfx_current == NULL)
3562 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3564 artwork.snd_current =
3565 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3566 if (artwork.snd_current == NULL)
3567 artwork.snd_current =
3568 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3569 if (artwork.snd_current == NULL)
3570 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3572 artwork.mus_current =
3573 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3574 if (artwork.mus_current == NULL)
3575 artwork.mus_current =
3576 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3577 if (artwork.mus_current == NULL)
3578 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3580 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3581 artwork.snd_current_identifier = artwork.snd_current->identifier;
3582 artwork.mus_current_identifier = artwork.mus_current->identifier;
3585 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3586 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3587 printf("music set == %s\n\n", artwork.mus_current_identifier);
3590 sortTreeInfo(&artwork.gfx_first);
3591 sortTreeInfo(&artwork.snd_first);
3592 sortTreeInfo(&artwork.mus_first);
3595 dumpTreeInfo(artwork.gfx_first, 0);
3596 dumpTreeInfo(artwork.snd_first, 0);
3597 dumpTreeInfo(artwork.mus_first, 0);
3601 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3602 LevelDirTree *level_node)
3605 static unsigned long progress_delay = 0;
3606 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3608 int type = (*artwork_node)->type;
3610 /* recursively check all level directories for artwork sub-directories */
3614 /* check all tree entries for artwork, but skip parent link entries */
3615 if (!level_node->parent_link)
3617 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3618 boolean cached = (artwork_new != NULL);
3622 pushTreeInfo(artwork_node, artwork_new);
3626 TreeInfo *topnode_last = *artwork_node;
3627 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3628 ARTWORK_DIRECTORY(type));
3630 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3632 if (topnode_last != *artwork_node) /* check for newly added node */
3634 artwork_new = *artwork_node;
3636 setString(&artwork_new->identifier, level_node->subdir);
3637 setString(&artwork_new->name, level_node->name);
3638 setString(&artwork_new->name_sorting, level_node->name_sorting);
3640 artwork_new->sort_priority = level_node->sort_priority;
3641 artwork_new->color = LEVELCOLOR(artwork_new);
3647 /* insert artwork info (from old cache or filesystem) into new cache */
3648 if (artwork_new != NULL)
3649 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3653 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3654 level_node->level_group);
3656 if (level_node->level_group ||
3657 DelayReached(&progress_delay, progress_delay_value))
3658 DrawInitText(level_node->name, 150, FC_YELLOW);
3661 if (level_node->node_group != NULL)
3662 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3664 level_node = level_node->next;
3668 void LoadLevelArtworkInfo()
3670 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3672 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3673 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3674 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3676 SaveArtworkInfoCache();
3678 /* needed for reloading level artwork not known at ealier stage */
3680 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3682 artwork.gfx_current =
3683 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3684 if (artwork.gfx_current == NULL)
3685 artwork.gfx_current =
3686 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3687 if (artwork.gfx_current == NULL)
3688 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3691 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3693 artwork.snd_current =
3694 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3695 if (artwork.snd_current == NULL)
3696 artwork.snd_current =
3697 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3698 if (artwork.snd_current == NULL)
3699 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3702 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3704 artwork.mus_current =
3705 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3706 if (artwork.mus_current == NULL)
3707 artwork.mus_current =
3708 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3709 if (artwork.mus_current == NULL)
3710 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3713 sortTreeInfo(&artwork.gfx_first);
3714 sortTreeInfo(&artwork.snd_first);
3715 sortTreeInfo(&artwork.mus_first);
3718 dumpTreeInfo(artwork.gfx_first, 0);
3719 dumpTreeInfo(artwork.snd_first, 0);
3720 dumpTreeInfo(artwork.mus_first, 0);
3724 static void SaveUserLevelInfo()
3726 LevelDirTree *level_info;
3731 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3733 if (!(file = fopen(filename, MODE_WRITE)))
3735 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3740 level_info = newTreeInfo();
3742 /* always start with reliable default values */
3743 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3745 setString(&level_info->name, getLoginName());
3746 setString(&level_info->author, getRealName());
3747 level_info->levels = 100;
3748 level_info->first_level = 1;
3750 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3752 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3753 getCookie("LEVELINFO")));
3756 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3758 if (i == LEVELINFO_TOKEN_NAME ||
3759 i == LEVELINFO_TOKEN_AUTHOR ||
3760 i == LEVELINFO_TOKEN_LEVELS ||
3761 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3762 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3764 /* just to make things nicer :) */
3765 if (i == LEVELINFO_TOKEN_AUTHOR)
3766 fprintf(file, "\n");
3769 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3773 SetFilePermissions(filename, PERMS_PRIVATE);
3775 freeTreeInfo(level_info);
3779 char *getSetupValue(int type, void *value)
3781 static char value_string[MAX_LINE_LEN];
3789 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3793 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3797 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3798 *(int *)value == FALSE ? "off" : "on"));
3802 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3805 case TYPE_YES_NO_AUTO:
3806 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3807 *(int *)value == FALSE ? "no" : "yes"));
3811 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3815 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3819 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3823 sprintf(value_string, "%d", *(int *)value);
3827 if (*(char **)value == NULL)
3830 strcpy(value_string, *(char **)value);
3834 value_string[0] = '\0';
3838 if (type & TYPE_GHOSTED)
3839 strcpy(value_string, "n/a");
3841 return value_string;
3844 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3848 static char token_string[MAX_LINE_LEN];
3849 int token_type = token_info[token_nr].type;
3850 void *setup_value = token_info[token_nr].value;
3851 char *token_text = token_info[token_nr].text;
3852 char *value_string = getSetupValue(token_type, setup_value);
3854 /* build complete token string */
3855 sprintf(token_string, "%s%s", prefix, token_text);
3857 /* build setup entry line */
3858 line = getFormattedSetupEntry(token_string, value_string);
3860 if (token_type == TYPE_KEY_X11)
3862 Key key = *(Key *)setup_value;
3863 char *keyname = getKeyNameFromKey(key);
3865 /* add comment, if useful */
3866 if (!strEqual(keyname, "(undefined)") &&
3867 !strEqual(keyname, "(unknown)"))
3869 /* add at least one whitespace */
3871 for (i = strlen(line); i < token_comment_position; i++)
3875 strcat(line, keyname);
3882 void LoadLevelSetup_LastSeries()
3884 /* ----------------------------------------------------------------------- */
3885 /* ~/.<program>/levelsetup.conf */
3886 /* ----------------------------------------------------------------------- */
3888 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3889 SetupFileHash *level_setup_hash = NULL;
3891 /* always start with reliable default values */
3892 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3894 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3895 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3897 if (leveldir_current == NULL)
3898 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3901 if ((level_setup_hash = loadSetupFileHash(filename)))
3903 char *last_level_series =
3904 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3906 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3908 if (leveldir_current == NULL)
3909 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3911 checkSetupFileHashIdentifier(level_setup_hash, filename,
3912 getCookie("LEVELSETUP"));
3914 freeSetupFileHash(level_setup_hash);
3917 Error(ERR_WARN, "using default setup values");
3922 void SaveLevelSetup_LastSeries()
3924 /* ----------------------------------------------------------------------- */
3925 /* ~/.<program>/levelsetup.conf */
3926 /* ----------------------------------------------------------------------- */
3928 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3929 char *level_subdir = leveldir_current->subdir;
3932 InitUserDataDirectory();
3934 if (!(file = fopen(filename, MODE_WRITE)))
3936 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3941 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3942 getCookie("LEVELSETUP")));
3943 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3948 SetFilePermissions(filename, PERMS_PRIVATE);
3953 static void checkSeriesInfo()
3955 static char *level_directory = NULL;
3957 struct dirent *dir_entry;
3959 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3961 level_directory = getPath2((leveldir_current->in_user_dir ?
3962 getUserLevelDir(NULL) :
3963 options.level_directory),
3964 leveldir_current->fullpath);
3966 if ((dir = opendir(level_directory)) == NULL)
3968 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3972 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3974 if (strlen(dir_entry->d_name) > 4 &&
3975 dir_entry->d_name[3] == '.' &&
3976 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3978 char levelnum_str[4];
3981 strncpy(levelnum_str, dir_entry->d_name, 3);
3982 levelnum_str[3] = '\0';
3984 levelnum_value = atoi(levelnum_str);
3987 if (levelnum_value < leveldir_current->first_level)
3989 Error(ERR_WARN, "additional level %d found", levelnum_value);
3990 leveldir_current->first_level = levelnum_value;
3992 else if (levelnum_value > leveldir_current->last_level)
3994 Error(ERR_WARN, "additional level %d found", levelnum_value);
3995 leveldir_current->last_level = levelnum_value;
4004 void LoadLevelSetup_SeriesInfo()
4007 SetupFileHash *level_setup_hash = NULL;
4008 char *level_subdir = leveldir_current->subdir;
4010 /* always start with reliable default values */
4011 level_nr = leveldir_current->first_level;
4013 checkSeriesInfo(leveldir_current);
4015 /* ----------------------------------------------------------------------- */
4016 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4017 /* ----------------------------------------------------------------------- */
4019 level_subdir = leveldir_current->subdir;
4021 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4023 if ((level_setup_hash = loadSetupFileHash(filename)))
4027 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4031 level_nr = atoi(token_value);
4033 if (level_nr < leveldir_current->first_level)
4034 level_nr = leveldir_current->first_level;
4035 if (level_nr > leveldir_current->last_level)
4036 level_nr = leveldir_current->last_level;
4039 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4043 int level_nr = atoi(token_value);
4045 if (level_nr < leveldir_current->first_level)
4046 level_nr = leveldir_current->first_level;
4047 if (level_nr > leveldir_current->last_level + 1)
4048 level_nr = leveldir_current->last_level;
4050 if (leveldir_current->user_defined || !leveldir_current->handicap)
4051 level_nr = leveldir_current->last_level;
4053 leveldir_current->handicap_level = level_nr;
4056 checkSetupFileHashIdentifier(level_setup_hash, filename,
4057 getCookie("LEVELSETUP"));
4059 freeSetupFileHash(level_setup_hash);
4062 Error(ERR_WARN, "using default setup values");
4067 void SaveLevelSetup_SeriesInfo()
4070 char *level_subdir = leveldir_current->subdir;
4071 char *level_nr_str = int2str(level_nr, 0);
4072 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4075 /* ----------------------------------------------------------------------- */
4076 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4077 /* ----------------------------------------------------------------------- */
4079 InitLevelSetupDirectory(level_subdir);
4081 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4083 if (!(file = fopen(filename, MODE_WRITE)))
4085 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4090 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4091 getCookie("LEVELSETUP")));
4092 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4094 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4095 handicap_level_str));
4099 SetFilePermissions(filename, PERMS_PRIVATE);