1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getClassicArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getClassicArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
338 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
343 No (or non-existing) artwork configured in "levelinfo.conf". This would
344 normally result in using the artwork configured in the setup menu. But
345 if an artwork subdirectory exists (which might contain custom artwork
346 or an artwork configuration file), this level artwork must be treated
347 as relative to the default "classic" artwork, not to the artwork that
348 is currently configured in the setup menu.
350 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
351 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
352 the real "classic" artwork from the original R'n'D (like "gfx_classic").
355 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
357 checked_free(*artwork_set_ptr);
361 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
362 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
366 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
367 *artwork_set_ptr = NULL;
373 return *artwork_set_ptr;
376 inline static char *getLevelArtworkSet(int type)
378 if (leveldir_current == NULL)
381 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
384 inline static char *getLevelArtworkDir(int type)
386 if (leveldir_current == NULL)
387 return UNDEFINED_FILENAME;
389 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
392 char *getTapeFilename(int nr)
394 static char *filename = NULL;
395 char basename[MAX_FILENAME_LEN];
397 checked_free(filename);
399 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
400 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
405 char *getSolutionTapeFilename(int nr)
407 static char *filename = NULL;
408 char basename[MAX_FILENAME_LEN];
410 checked_free(filename);
412 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
413 filename = getPath2(getSolutionTapeDir(), basename);
418 char *getScoreFilename(int nr)
420 static char *filename = NULL;
421 char basename[MAX_FILENAME_LEN];
423 checked_free(filename);
425 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
426 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
431 char *getSetupFilename()
433 static char *filename = NULL;
435 checked_free(filename);
437 filename = getPath2(getSetupDir(), SETUP_FILENAME);
442 char *getEditorSetupFilename()
444 static char *filename = NULL;
446 checked_free(filename);
447 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
449 if (fileExists(filename))
452 checked_free(filename);
453 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
458 char *getHelpAnimFilename()
460 static char *filename = NULL;
462 checked_free(filename);
464 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
469 char *getHelpTextFilename()
471 static char *filename = NULL;
473 checked_free(filename);
475 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
480 char *getLevelSetInfoFilename()
482 static char *filename = NULL;
497 for (i = 0; basenames[i] != NULL; i++)
499 checked_free(filename);
500 filename = getPath2(getCurrentLevelDir(), basenames[i]);
502 if (fileExists(filename))
509 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
511 static char basename[32];
513 sprintf(basename, "%s_%d.txt",
514 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
519 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
521 static char *filename = NULL;
523 boolean skip_setup_artwork = FALSE;
525 checked_free(filename);
527 basename = getLevelSetTitleMessageBasename(nr, initial);
529 if (!gfx.override_level_graphics)
531 /* 1st try: look for special artwork in current level series directory */
532 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
533 if (fileExists(filename))
538 /* 2nd try: look for message file in current level set directory */
539 filename = getPath2(getCurrentLevelDir(), basename);
540 if (fileExists(filename))
545 /* check if there is special artwork configured in level series config */
546 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
548 /* 3rd try: look for special artwork configured in level series config */
549 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
550 if (fileExists(filename))
555 /* take missing artwork configured in level set config from default */
556 skip_setup_artwork = TRUE;
560 if (!skip_setup_artwork)
562 /* 4th try: look for special artwork in configured artwork directory */
563 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
564 if (fileExists(filename))
570 /* 5th try: look for default artwork in new default artwork directory */
571 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
572 if (fileExists(filename))
577 /* 6th try: look for default artwork in old default artwork directory */
578 filename = getPath2(options.graphics_directory, basename);
579 if (fileExists(filename))
582 return NULL; /* cannot find specified artwork file anywhere */
585 static char *getCorrectedArtworkBasename(char *basename)
587 char *basename_corrected = basename;
589 #if defined(PLATFORM_MSDOS)
590 if (program.filename_prefix != NULL)
592 int prefix_len = strlen(program.filename_prefix);
594 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
595 basename_corrected = &basename[prefix_len];
597 /* if corrected filename is still longer than standard MS-DOS filename
598 size (8 characters + 1 dot + 3 characters file extension), shorten
599 filename by writing file extension after 8th basename character */
600 if (strlen(basename_corrected) > 8 + 1 + 3)
602 static char *msdos_filename = NULL;
604 checked_free(msdos_filename);
606 msdos_filename = getStringCopy(basename_corrected);
607 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
609 basename_corrected = msdos_filename;
614 return basename_corrected;
617 char *getCustomImageFilename(char *basename)
619 static char *filename = NULL;
620 boolean skip_setup_artwork = FALSE;
622 checked_free(filename);
624 basename = getCorrectedArtworkBasename(basename);
626 if (!gfx.override_level_graphics)
628 /* 1st try: look for special artwork in current level series directory */
629 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
630 if (fileExists(filename))
635 /* check if there is special artwork configured in level series config */
636 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
638 /* 2nd try: look for special artwork configured in level series config */
639 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
640 if (fileExists(filename))
645 /* take missing artwork configured in level set config from default */
646 skip_setup_artwork = TRUE;
650 if (!skip_setup_artwork)
652 /* 3rd try: look for special artwork in configured artwork directory */
653 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
654 if (fileExists(filename))
660 /* 4th try: look for default artwork in new default artwork directory */
661 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
662 if (fileExists(filename))
667 /* 5th try: look for default artwork in old default artwork directory */
668 filename = getPath2(options.graphics_directory, basename);
669 if (fileExists(filename))
672 #if defined(CREATE_SPECIAL_EDITION)
676 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
678 /* 6th try: look for fallback artwork in old default artwork directory */
679 /* (needed to prevent errors when trying to access unused artwork files) */
680 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
681 if (fileExists(filename))
685 return NULL; /* cannot find specified artwork file anywhere */
688 char *getCustomSoundFilename(char *basename)
690 static char *filename = NULL;
691 boolean skip_setup_artwork = FALSE;
693 checked_free(filename);
695 basename = getCorrectedArtworkBasename(basename);
697 if (!gfx.override_level_sounds)
699 /* 1st try: look for special artwork in current level series directory */
700 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
701 if (fileExists(filename))
706 /* check if there is special artwork configured in level series config */
707 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
709 /* 2nd try: look for special artwork configured in level series config */
710 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
711 if (fileExists(filename))
716 /* take missing artwork configured in level set config from default */
717 skip_setup_artwork = TRUE;
721 if (!skip_setup_artwork)
723 /* 3rd try: look for special artwork in configured artwork directory */
724 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
725 if (fileExists(filename))
731 /* 4th try: look for default artwork in new default artwork directory */
732 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
733 if (fileExists(filename))
738 /* 5th try: look for default artwork in old default artwork directory */
739 filename = getPath2(options.sounds_directory, basename);
740 if (fileExists(filename))
743 #if defined(CREATE_SPECIAL_EDITION)
747 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
749 /* 6th try: look for fallback artwork in old default artwork directory */
750 /* (needed to prevent errors when trying to access unused artwork files) */
751 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
752 if (fileExists(filename))
756 return NULL; /* cannot find specified artwork file anywhere */
759 char *getCustomMusicFilename(char *basename)
761 static char *filename = NULL;
762 boolean skip_setup_artwork = FALSE;
764 checked_free(filename);
766 basename = getCorrectedArtworkBasename(basename);
768 if (!gfx.override_level_music)
770 /* 1st try: look for special artwork in current level series directory */
771 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
772 if (fileExists(filename))
777 /* check if there is special artwork configured in level series config */
778 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
780 /* 2nd try: look for special artwork configured in level series config */
781 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
782 if (fileExists(filename))
787 /* take missing artwork configured in level set config from default */
788 skip_setup_artwork = TRUE;
792 if (!skip_setup_artwork)
794 /* 3rd try: look for special artwork in configured artwork directory */
795 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
796 if (fileExists(filename))
802 /* 4th try: look for default artwork in new default artwork directory */
803 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
804 if (fileExists(filename))
809 /* 5th try: look for default artwork in old default artwork directory */
810 filename = getPath2(options.music_directory, basename);
811 if (fileExists(filename))
814 #if defined(CREATE_SPECIAL_EDITION)
818 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
820 /* 6th try: look for fallback artwork in old default artwork directory */
821 /* (needed to prevent errors when trying to access unused artwork files) */
822 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
823 if (fileExists(filename))
827 return NULL; /* cannot find specified artwork file anywhere */
830 char *getCustomArtworkFilename(char *basename, int type)
832 if (type == ARTWORK_TYPE_GRAPHICS)
833 return getCustomImageFilename(basename);
834 else if (type == ARTWORK_TYPE_SOUNDS)
835 return getCustomSoundFilename(basename);
836 else if (type == ARTWORK_TYPE_MUSIC)
837 return getCustomMusicFilename(basename);
839 return UNDEFINED_FILENAME;
842 char *getCustomArtworkConfigFilename(int type)
844 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
847 char *getCustomArtworkLevelConfigFilename(int type)
849 static char *filename = NULL;
851 checked_free(filename);
853 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
858 char *getCustomMusicDirectory(void)
860 static char *directory = NULL;
861 boolean skip_setup_artwork = FALSE;
863 checked_free(directory);
865 if (!gfx.override_level_music)
867 /* 1st try: look for special artwork in current level series directory */
868 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
869 if (fileExists(directory))
874 /* check if there is special artwork configured in level series config */
875 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
877 /* 2nd try: look for special artwork configured in level series config */
878 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
879 if (fileExists(directory))
884 /* take missing artwork configured in level set config from default */
885 skip_setup_artwork = TRUE;
889 if (!skip_setup_artwork)
891 /* 3rd try: look for special artwork in configured artwork directory */
892 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
893 if (fileExists(directory))
899 /* 4th try: look for default artwork in new default artwork directory */
900 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
901 if (fileExists(directory))
906 /* 5th try: look for default artwork in old default artwork directory */
907 directory = getStringCopy(options.music_directory);
908 if (fileExists(directory))
911 return NULL; /* cannot find specified artwork file anywhere */
914 void InitTapeDirectory(char *level_subdir)
916 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
917 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
918 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
921 void InitScoreDirectory(char *level_subdir)
923 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
924 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
925 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
928 static void SaveUserLevelInfo();
930 void InitUserLevelDirectory(char *level_subdir)
932 if (!fileExists(getUserLevelDir(level_subdir)))
934 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
935 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
936 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
942 void InitLevelSetupDirectory(char *level_subdir)
944 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
945 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
946 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
949 void InitCacheDirectory()
951 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
952 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
956 /* ------------------------------------------------------------------------- */
957 /* some functions to handle lists of level and artwork directories */
958 /* ------------------------------------------------------------------------- */
960 TreeInfo *newTreeInfo()
962 return checked_calloc(sizeof(TreeInfo));
965 TreeInfo *newTreeInfo_setDefaults(int type)
967 TreeInfo *ti = newTreeInfo();
969 setTreeInfoToDefaults(ti, type);
974 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
976 node_new->next = *node_first;
977 *node_first = node_new;
980 int numTreeInfo(TreeInfo *node)
993 boolean validLevelSeries(TreeInfo *node)
995 return (node != NULL && !node->node_group && !node->parent_link);
998 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1003 if (node->node_group) /* enter level group (step down into tree) */
1004 return getFirstValidTreeInfoEntry(node->node_group);
1005 else if (node->parent_link) /* skip start entry of level group */
1007 if (node->next) /* get first real level series entry */
1008 return getFirstValidTreeInfoEntry(node->next);
1009 else /* leave empty level group and go on */
1010 return getFirstValidTreeInfoEntry(node->node_parent->next);
1012 else /* this seems to be a regular level series */
1016 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1021 if (node->node_parent == NULL) /* top level group */
1022 return *node->node_top;
1023 else /* sub level group */
1024 return node->node_parent->node_group;
1027 int numTreeInfoInGroup(TreeInfo *node)
1029 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1032 int posTreeInfo(TreeInfo *node)
1034 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1039 if (node_cmp == node)
1043 node_cmp = node_cmp->next;
1049 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1051 TreeInfo *node_default = node;
1063 return node_default;
1066 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1068 if (identifier == NULL)
1073 if (node->node_group)
1075 TreeInfo *node_group;
1077 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1082 else if (!node->parent_link)
1084 if (strEqual(identifier, node->identifier))
1094 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1095 TreeInfo *node, boolean skip_sets_without_levels)
1102 if (!node->parent_link && !node->level_group &&
1103 skip_sets_without_levels && node->levels == 0)
1104 return cloneTreeNode(node_top, node_parent, node->next,
1105 skip_sets_without_levels);
1108 node_new = getTreeInfoCopy(node); /* copy complete node */
1110 node_new = newTreeInfo();
1112 *node_new = *node; /* copy complete node */
1115 node_new->node_top = node_top; /* correct top node link */
1116 node_new->node_parent = node_parent; /* correct parent node link */
1118 if (node->level_group)
1119 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1120 skip_sets_without_levels);
1122 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1123 skip_sets_without_levels);
1128 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1130 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1132 *ti_new = ti_cloned;
1135 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1137 boolean settings_changed = FALSE;
1141 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1142 !strEqual(node->graphics_set, node->graphics_set_ecs))
1144 setString(&node->graphics_set, node->graphics_set_ecs);
1145 settings_changed = TRUE;
1147 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1148 !strEqual(node->graphics_set, node->graphics_set_aga))
1150 setString(&node->graphics_set, node->graphics_set_aga);
1151 settings_changed = TRUE;
1154 if (node->node_group != NULL)
1155 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1160 return settings_changed;
1163 void dumpTreeInfo(TreeInfo *node, int depth)
1167 printf("Dumping TreeInfo:\n");
1171 for (i = 0; i < (depth + 1) * 3; i++)
1174 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1175 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1177 if (node->node_group != NULL)
1178 dumpTreeInfo(node->node_group, depth + 1);
1184 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1185 int (*compare_function)(const void *,
1188 int num_nodes = numTreeInfo(*node_first);
1189 TreeInfo **sort_array;
1190 TreeInfo *node = *node_first;
1196 /* allocate array for sorting structure pointers */
1197 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1199 /* writing structure pointers to sorting array */
1200 while (i < num_nodes && node) /* double boundary check... */
1202 sort_array[i] = node;
1208 /* sorting the structure pointers in the sorting array */
1209 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1212 /* update the linkage of list elements with the sorted node array */
1213 for (i = 0; i < num_nodes - 1; i++)
1214 sort_array[i]->next = sort_array[i + 1];
1215 sort_array[num_nodes - 1]->next = NULL;
1217 /* update the linkage of the main list anchor pointer */
1218 *node_first = sort_array[0];
1222 /* now recursively sort the level group structures */
1226 if (node->node_group != NULL)
1227 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1233 void sortTreeInfo(TreeInfo **node_first)
1235 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1239 /* ========================================================================= */
1240 /* some stuff from "files.c" */
1241 /* ========================================================================= */
1243 #if defined(PLATFORM_WIN32)
1245 #define S_IRGRP S_IRUSR
1248 #define S_IROTH S_IRUSR
1251 #define S_IWGRP S_IWUSR
1254 #define S_IWOTH S_IWUSR
1257 #define S_IXGRP S_IXUSR
1260 #define S_IXOTH S_IXUSR
1263 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1268 #endif /* PLATFORM_WIN32 */
1270 /* file permissions for newly written files */
1271 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1272 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1273 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1275 #define MODE_W_PRIVATE (S_IWUSR)
1276 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1277 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1279 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1280 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1282 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1283 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1287 static char *dir = NULL;
1289 #if defined(PLATFORM_WIN32)
1292 dir = checked_malloc(MAX_PATH + 1);
1294 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1297 #elif defined(PLATFORM_UNIX)
1300 if ((dir = getenv("HOME")) == NULL)
1304 if ((pwd = getpwuid(getuid())) != NULL)
1305 dir = getStringCopy(pwd->pw_dir);
1317 char *getCommonDataDir(void)
1319 static char *common_data_dir = NULL;
1321 #if defined(PLATFORM_WIN32)
1322 if (common_data_dir == NULL)
1324 char *dir = checked_malloc(MAX_PATH + 1);
1326 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1327 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1328 common_data_dir = getPath2(dir, program.userdata_subdir);
1330 common_data_dir = options.rw_base_directory;
1333 if (common_data_dir == NULL)
1334 common_data_dir = options.rw_base_directory;
1337 return common_data_dir;
1340 char *getPersonalDataDir(void)
1342 static char *personal_data_dir = NULL;
1344 #if defined(PLATFORM_MACOSX)
1345 if (personal_data_dir == NULL)
1346 personal_data_dir = getPath2(getHomeDir(), "Documents");
1348 if (personal_data_dir == NULL)
1349 personal_data_dir = getHomeDir();
1352 return personal_data_dir;
1355 char *getUserGameDataDir(void)
1357 static char *user_game_data_dir = NULL;
1359 if (user_game_data_dir == NULL)
1360 user_game_data_dir = getPath2(getPersonalDataDir(),
1361 program.userdata_subdir);
1363 return user_game_data_dir;
1366 void updateUserGameDataDir()
1368 #if defined(PLATFORM_MACOSX)
1369 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1370 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1372 /* convert old Unix style game data directory to Mac OS X style, if needed */
1373 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1375 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1377 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1378 userdata_dir_old, userdata_dir_new);
1380 /* continue using Unix style data directory -- this should not happen */
1381 program.userdata_path = getPath2(getPersonalDataDir(),
1382 program.userdata_subdir_unix);
1386 free(userdata_dir_old);
1392 return getUserGameDataDir();
1395 static mode_t posix_umask(mode_t mask)
1397 #if defined(PLATFORM_UNIX)
1404 static int posix_mkdir(const char *pathname, mode_t mode)
1406 #if defined(PLATFORM_WIN32)
1407 return mkdir(pathname);
1409 return mkdir(pathname, mode);
1413 void createDirectory(char *dir, char *text, int permission_class)
1415 /* leave "other" permissions in umask untouched, but ensure group parts
1416 of USERDATA_DIR_MODE are not masked */
1417 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1418 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1419 mode_t normal_umask = posix_umask(0);
1420 mode_t group_umask = ~(dir_mode & S_IRWXG);
1421 posix_umask(normal_umask & group_umask);
1423 if (!fileExists(dir))
1424 if (posix_mkdir(dir, dir_mode) != 0)
1425 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1427 posix_umask(normal_umask); /* reset normal umask */
1430 void InitUserDataDirectory()
1432 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1435 void SetFilePermissions(char *filename, int permission_class)
1437 chmod(filename, (permission_class == PERMS_PRIVATE ?
1438 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1441 char *getCookie(char *file_type)
1443 static char cookie[MAX_COOKIE_LEN + 1];
1445 if (strlen(program.cookie_prefix) + 1 +
1446 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1447 return "[COOKIE ERROR]"; /* should never happen */
1449 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1450 program.cookie_prefix, file_type,
1451 program.version_major, program.version_minor);
1456 int getFileVersionFromCookieString(const char *cookie)
1458 const char *ptr_cookie1, *ptr_cookie2;
1459 const char *pattern1 = "_FILE_VERSION_";
1460 const char *pattern2 = "?.?";
1461 const int len_cookie = strlen(cookie);
1462 const int len_pattern1 = strlen(pattern1);
1463 const int len_pattern2 = strlen(pattern2);
1464 const int len_pattern = len_pattern1 + len_pattern2;
1465 int version_major, version_minor;
1467 if (len_cookie <= len_pattern)
1470 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1471 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1473 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1476 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1477 ptr_cookie2[1] != '.' ||
1478 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1481 version_major = ptr_cookie2[0] - '0';
1482 version_minor = ptr_cookie2[2] - '0';
1484 return VERSION_IDENT(version_major, version_minor, 0, 0);
1487 boolean checkCookieString(const char *cookie, const char *template)
1489 const char *pattern = "_FILE_VERSION_?.?";
1490 const int len_cookie = strlen(cookie);
1491 const int len_template = strlen(template);
1492 const int len_pattern = strlen(pattern);
1494 if (len_cookie != len_template)
1497 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1503 /* ------------------------------------------------------------------------- */
1504 /* setup file list and hash handling functions */
1505 /* ------------------------------------------------------------------------- */
1507 char *getFormattedSetupEntry(char *token, char *value)
1510 static char entry[MAX_LINE_LEN];
1512 /* if value is an empty string, just return token without value */
1516 /* start with the token and some spaces to format output line */
1517 sprintf(entry, "%s:", token);
1518 for (i = strlen(entry); i < token_value_position; i++)
1521 /* continue with the token's value */
1522 strcat(entry, value);
1527 SetupFileList *newSetupFileList(char *token, char *value)
1529 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1531 new->token = getStringCopy(token);
1532 new->value = getStringCopy(value);
1539 void freeSetupFileList(SetupFileList *list)
1544 checked_free(list->token);
1545 checked_free(list->value);
1548 freeSetupFileList(list->next);
1553 char *getListEntry(SetupFileList *list, char *token)
1558 if (strEqual(list->token, token))
1561 return getListEntry(list->next, token);
1564 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1569 if (strEqual(list->token, token))
1571 checked_free(list->value);
1573 list->value = getStringCopy(value);
1577 else if (list->next == NULL)
1578 return (list->next = newSetupFileList(token, value));
1580 return setListEntry(list->next, token, value);
1583 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1588 if (list->next == NULL)
1589 return (list->next = newSetupFileList(token, value));
1591 return addListEntry(list->next, token, value);
1595 static void printSetupFileList(SetupFileList *list)
1600 printf("token: '%s'\n", list->token);
1601 printf("value: '%s'\n", list->value);
1603 printSetupFileList(list->next);
1608 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1609 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1610 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1611 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1613 #define insert_hash_entry hashtable_insert
1614 #define search_hash_entry hashtable_search
1615 #define change_hash_entry hashtable_change
1616 #define remove_hash_entry hashtable_remove
1619 static unsigned int get_hash_from_key(void *key)
1624 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1625 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1626 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1627 it works better than many other constants, prime or not) has never been
1628 adequately explained.
1630 If you just want to have a good hash function, and cannot wait, djb2
1631 is one of the best string hash functions i know. It has excellent
1632 distribution and speed on many different sets of keys and table sizes.
1633 You are not likely to do better with one of the "well known" functions
1634 such as PJW, K&R, etc.
1636 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1639 char *str = (char *)key;
1640 unsigned int hash = 5381;
1643 while ((c = *str++))
1644 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1649 static int keys_are_equal(void *key1, void *key2)
1651 return (strEqual((char *)key1, (char *)key2));
1654 SetupFileHash *newSetupFileHash()
1656 SetupFileHash *new_hash =
1657 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1659 if (new_hash == NULL)
1660 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1665 void freeSetupFileHash(SetupFileHash *hash)
1670 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1673 char *getHashEntry(SetupFileHash *hash, char *token)
1678 return search_hash_entry(hash, token);
1681 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1688 value_copy = getStringCopy(value);
1690 /* change value; if it does not exist, insert it as new */
1691 if (!change_hash_entry(hash, token, value_copy))
1692 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1693 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1696 char *removeHashEntry(SetupFileHash *hash, char *token)
1701 return remove_hash_entry(hash, token);
1705 static void printSetupFileHash(SetupFileHash *hash)
1707 BEGIN_HASH_ITERATION(hash, itr)
1709 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1710 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1712 END_HASH_ITERATION(hash, itr)
1716 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1717 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1718 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1720 static boolean token_value_separator_found = FALSE;
1721 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1722 static boolean token_value_separator_warning = FALSE;
1724 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1725 static boolean token_already_exists_warning = FALSE;
1728 static boolean getTokenValueFromSetupLineExt(char *line,
1729 char **token_ptr, char **value_ptr,
1730 char *filename, char *line_raw,
1732 boolean separator_required)
1734 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1735 char *token, *value, *line_ptr;
1737 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1738 if (line_raw == NULL)
1740 strncpy(line_copy, line, MAX_LINE_LEN);
1741 line_copy[MAX_LINE_LEN] = '\0';
1744 strcpy(line_raw_copy, line_copy);
1745 line_raw = line_raw_copy;
1748 /* cut trailing comment from input line */
1749 for (line_ptr = line; *line_ptr; line_ptr++)
1751 if (*line_ptr == '#')
1758 /* cut trailing whitespaces from input line */
1759 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1760 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1763 /* ignore empty lines */
1767 /* cut leading whitespaces from token */
1768 for (token = line; *token; token++)
1769 if (*token != ' ' && *token != '\t')
1772 /* start with empty value as reliable default */
1775 token_value_separator_found = FALSE;
1777 /* find end of token to determine start of value */
1778 for (line_ptr = token; *line_ptr; line_ptr++)
1781 /* first look for an explicit token/value separator, like ':' or '=' */
1782 if (*line_ptr == ':' || *line_ptr == '=')
1784 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1787 *line_ptr = '\0'; /* terminate token string */
1788 value = line_ptr + 1; /* set beginning of value */
1790 token_value_separator_found = TRUE;
1796 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1797 /* fallback: if no token/value separator found, also allow whitespaces */
1798 if (!token_value_separator_found && !separator_required)
1800 for (line_ptr = token; *line_ptr; line_ptr++)
1802 if (*line_ptr == ' ' || *line_ptr == '\t')
1804 *line_ptr = '\0'; /* terminate token string */
1805 value = line_ptr + 1; /* set beginning of value */
1807 token_value_separator_found = TRUE;
1813 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1814 if (token_value_separator_found)
1816 if (!token_value_separator_warning)
1818 Error(ERR_INFO_LINE, "-");
1820 if (filename != NULL)
1822 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1823 Error(ERR_INFO, "- config file: '%s'", filename);
1827 Error(ERR_WARN, "missing token/value separator(s):");
1830 token_value_separator_warning = TRUE;
1833 if (filename != NULL)
1834 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1836 Error(ERR_INFO, "- line: '%s'", line_raw);
1842 /* cut trailing whitespaces from token */
1843 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1844 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1847 /* cut leading whitespaces from value */
1848 for (; *value; value++)
1849 if (*value != ' ' && *value != '\t')
1854 value = "true"; /* treat tokens without value as "true" */
1863 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1865 /* while the internal (old) interface does not require a token/value
1866 separator (for downwards compatibility with existing files which
1867 don't use them), it is mandatory for the external (new) interface */
1869 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1873 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1874 boolean top_recursion_level, boolean is_hash)
1876 static SetupFileHash *include_filename_hash = NULL;
1877 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1878 char *token, *value, *line_ptr;
1879 void *insert_ptr = NULL;
1880 boolean read_continued_line = FALSE;
1882 int line_nr = 0, token_count = 0, include_count = 0;
1884 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1885 token_value_separator_warning = FALSE;
1888 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1889 token_already_exists_warning = FALSE;
1892 if (!(file = fopen(filename, MODE_READ)))
1894 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1899 /* use "insert pointer" to store list end for constant insertion complexity */
1901 insert_ptr = setup_file_data;
1903 /* on top invocation, create hash to mark included files (to prevent loops) */
1904 if (top_recursion_level)
1905 include_filename_hash = newSetupFileHash();
1907 /* mark this file as already included (to prevent including it again) */
1908 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1912 /* read next line of input file */
1913 if (!fgets(line, MAX_LINE_LEN, file))
1916 /* check if line was completely read and is terminated by line break */
1917 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1920 /* cut trailing line break (this can be newline and/or carriage return) */
1921 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1922 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1925 /* copy raw input line for later use (mainly debugging output) */
1926 strcpy(line_raw, line);
1928 if (read_continued_line)
1931 /* !!! ??? WHY ??? !!! */
1932 /* cut leading whitespaces from input line */
1933 for (line_ptr = line; *line_ptr; line_ptr++)
1934 if (*line_ptr != ' ' && *line_ptr != '\t')
1938 /* append new line to existing line, if there is enough space */
1939 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1940 strcat(previous_line, line_ptr);
1942 strcpy(line, previous_line); /* copy storage buffer to line */
1944 read_continued_line = FALSE;
1947 /* if the last character is '\', continue at next line */
1948 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1950 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1951 strcpy(previous_line, line); /* copy line to storage buffer */
1953 read_continued_line = TRUE;
1958 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1959 line_raw, line_nr, FALSE))
1964 if (strEqual(token, "include"))
1966 if (getHashEntry(include_filename_hash, value) == NULL)
1968 char *basepath = getBasePath(filename);
1969 char *basename = getBaseName(value);
1970 char *filename_include = getPath2(basepath, basename);
1973 Error(ERR_INFO, "[including file '%s']", filename_include);
1976 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1980 free(filename_include);
1986 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1993 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1995 getHashEntry((SetupFileHash *)setup_file_data, token);
1997 if (old_value != NULL)
1999 if (!token_already_exists_warning)
2001 Error(ERR_INFO_LINE, "-");
2002 Error(ERR_WARN, "duplicate token(s) found in config file:");
2003 Error(ERR_INFO, "- config file: '%s'", filename);
2005 token_already_exists_warning = TRUE;
2008 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2009 Error(ERR_INFO, " old value: '%s'", old_value);
2010 Error(ERR_INFO, " new value: '%s'", value);
2014 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2018 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2028 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2029 if (token_value_separator_warning)
2030 Error(ERR_INFO_LINE, "-");
2033 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2034 if (token_already_exists_warning)
2035 Error(ERR_INFO_LINE, "-");
2038 if (token_count == 0 && include_count == 0)
2039 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2041 if (top_recursion_level)
2042 freeSetupFileHash(include_filename_hash);
2049 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2050 boolean top_recursion_level, boolean is_hash)
2052 static SetupFileHash *include_filename_hash = NULL;
2053 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2054 char *token, *value, *line_ptr;
2055 void *insert_ptr = NULL;
2056 boolean read_continued_line = FALSE;
2059 int token_count = 0;
2061 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2062 token_value_separator_warning = FALSE;
2065 if (!(file = fopen(filename, MODE_READ)))
2067 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2072 /* use "insert pointer" to store list end for constant insertion complexity */
2074 insert_ptr = setup_file_data;
2076 /* on top invocation, create hash to mark included files (to prevent loops) */
2077 if (top_recursion_level)
2078 include_filename_hash = newSetupFileHash();
2080 /* mark this file as already included (to prevent including it again) */
2081 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2085 /* read next line of input file */
2086 if (!fgets(line, MAX_LINE_LEN, file))
2089 /* check if line was completely read and is terminated by line break */
2090 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2093 /* cut trailing line break (this can be newline and/or carriage return) */
2094 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2095 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2098 /* copy raw input line for later use (mainly debugging output) */
2099 strcpy(line_raw, line);
2101 if (read_continued_line)
2103 /* cut leading whitespaces from input line */
2104 for (line_ptr = line; *line_ptr; line_ptr++)
2105 if (*line_ptr != ' ' && *line_ptr != '\t')
2108 /* append new line to existing line, if there is enough space */
2109 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2110 strcat(previous_line, line_ptr);
2112 strcpy(line, previous_line); /* copy storage buffer to line */
2114 read_continued_line = FALSE;
2117 /* if the last character is '\', continue at next line */
2118 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2120 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2121 strcpy(previous_line, line); /* copy line to storage buffer */
2123 read_continued_line = TRUE;
2128 /* cut trailing comment from input line */
2129 for (line_ptr = line; *line_ptr; line_ptr++)
2131 if (*line_ptr == '#')
2138 /* cut trailing whitespaces from input line */
2139 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2140 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2143 /* ignore empty lines */
2147 /* cut leading whitespaces from token */
2148 for (token = line; *token; token++)
2149 if (*token != ' ' && *token != '\t')
2152 /* start with empty value as reliable default */
2155 token_value_separator_found = FALSE;
2157 /* find end of token to determine start of value */
2158 for (line_ptr = token; *line_ptr; line_ptr++)
2161 /* first look for an explicit token/value separator, like ':' or '=' */
2162 if (*line_ptr == ':' || *line_ptr == '=')
2164 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2167 *line_ptr = '\0'; /* terminate token string */
2168 value = line_ptr + 1; /* set beginning of value */
2170 token_value_separator_found = TRUE;
2176 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2177 /* fallback: if no token/value separator found, also allow whitespaces */
2178 if (!token_value_separator_found)
2180 for (line_ptr = token; *line_ptr; line_ptr++)
2182 if (*line_ptr == ' ' || *line_ptr == '\t')
2184 *line_ptr = '\0'; /* terminate token string */
2185 value = line_ptr + 1; /* set beginning of value */
2187 token_value_separator_found = TRUE;
2193 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2194 if (token_value_separator_found)
2196 if (!token_value_separator_warning)
2198 Error(ERR_INFO_LINE, "-");
2199 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2200 Error(ERR_INFO, "- config file: '%s'", filename);
2202 token_value_separator_warning = TRUE;
2205 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2211 /* cut trailing whitespaces from token */
2212 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2213 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2216 /* cut leading whitespaces from value */
2217 for (; *value; value++)
2218 if (*value != ' ' && *value != '\t')
2223 value = "true"; /* treat tokens without value as "true" */
2228 if (strEqual(token, "include"))
2230 if (getHashEntry(include_filename_hash, value) == NULL)
2232 char *basepath = getBasePath(filename);
2233 char *basename = getBaseName(value);
2234 char *filename_include = getPath2(basepath, basename);
2237 Error(ERR_INFO, "[including file '%s']", filename_include);
2240 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2244 free(filename_include);
2248 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2254 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2256 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2265 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2266 if (token_value_separator_warning)
2267 Error(ERR_INFO_LINE, "-");
2270 if (token_count == 0)
2271 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2273 if (top_recursion_level)
2274 freeSetupFileHash(include_filename_hash);
2280 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2284 if (!(file = fopen(filename, MODE_WRITE)))
2286 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2291 BEGIN_HASH_ITERATION(hash, itr)
2293 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2294 HASH_ITERATION_VALUE(itr)));
2296 END_HASH_ITERATION(hash, itr)
2301 SetupFileList *loadSetupFileList(char *filename)
2303 SetupFileList *setup_file_list = newSetupFileList("", "");
2304 SetupFileList *first_valid_list_entry;
2306 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2308 freeSetupFileList(setup_file_list);
2313 first_valid_list_entry = setup_file_list->next;
2315 /* free empty list header */
2316 setup_file_list->next = NULL;
2317 freeSetupFileList(setup_file_list);
2319 return first_valid_list_entry;
2322 SetupFileHash *loadSetupFileHash(char *filename)
2324 SetupFileHash *setup_file_hash = newSetupFileHash();
2326 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2328 freeSetupFileHash(setup_file_hash);
2333 return setup_file_hash;
2336 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2337 char *filename, char *identifier)
2339 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2342 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2343 else if (!checkCookieString(value, identifier))
2344 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2348 /* ========================================================================= */
2349 /* setup file stuff */
2350 /* ========================================================================= */
2352 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2353 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2354 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2356 /* level directory info */
2357 #define LEVELINFO_TOKEN_IDENTIFIER 0
2358 #define LEVELINFO_TOKEN_NAME 1
2359 #define LEVELINFO_TOKEN_NAME_SORTING 2
2360 #define LEVELINFO_TOKEN_AUTHOR 3
2361 #define LEVELINFO_TOKEN_YEAR 4
2362 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2363 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2364 #define LEVELINFO_TOKEN_TESTED_BY 7
2365 #define LEVELINFO_TOKEN_LEVELS 8
2366 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2367 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2368 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2369 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2370 #define LEVELINFO_TOKEN_READONLY 13
2371 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2372 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2373 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2374 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2375 #define LEVELINFO_TOKEN_MUSIC_SET 18
2376 #define LEVELINFO_TOKEN_FILENAME 19
2377 #define LEVELINFO_TOKEN_FILETYPE 20
2378 #define LEVELINFO_TOKEN_HANDICAP 21
2379 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2381 #define NUM_LEVELINFO_TOKENS 23
2383 static LevelDirTree ldi;
2385 static struct TokenInfo levelinfo_tokens[] =
2387 /* level directory info */
2388 { TYPE_STRING, &ldi.identifier, "identifier" },
2389 { TYPE_STRING, &ldi.name, "name" },
2390 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2391 { TYPE_STRING, &ldi.author, "author" },
2392 { TYPE_STRING, &ldi.year, "year" },
2393 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2394 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2395 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2396 { TYPE_INTEGER, &ldi.levels, "levels" },
2397 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2398 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2399 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2400 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2401 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2402 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2403 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2404 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2405 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2406 { TYPE_STRING, &ldi.music_set, "music_set" },
2407 { TYPE_STRING, &ldi.level_filename, "filename" },
2408 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2409 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2410 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2413 static struct TokenInfo artworkinfo_tokens[] =
2415 /* artwork directory info */
2416 { TYPE_STRING, &ldi.identifier, "identifier" },
2417 { TYPE_STRING, &ldi.subdir, "subdir" },
2418 { TYPE_STRING, &ldi.name, "name" },
2419 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2420 { TYPE_STRING, &ldi.author, "author" },
2421 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2422 { TYPE_STRING, &ldi.basepath, "basepath" },
2423 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2424 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2425 { TYPE_INTEGER, &ldi.color, "color" },
2426 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2431 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2435 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2436 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2437 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2438 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2441 ti->node_parent = NULL;
2442 ti->node_group = NULL;
2449 ti->fullpath = NULL;
2450 ti->basepath = NULL;
2451 ti->identifier = NULL;
2452 ti->name = getStringCopy(ANONYMOUS_NAME);
2453 ti->name_sorting = NULL;
2454 ti->author = getStringCopy(ANONYMOUS_NAME);
2457 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2458 ti->latest_engine = FALSE; /* default: get from level */
2459 ti->parent_link = FALSE;
2460 ti->in_user_dir = FALSE;
2461 ti->user_defined = FALSE;
2463 ti->class_desc = NULL;
2465 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2467 if (ti->type == TREE_TYPE_LEVEL_DIR)
2469 ti->imported_from = NULL;
2470 ti->imported_by = NULL;
2471 ti->tested_by = NULL;
2473 ti->graphics_set_ecs = NULL;
2474 ti->graphics_set_aga = NULL;
2475 ti->graphics_set = NULL;
2476 ti->sounds_set = NULL;
2477 ti->music_set = NULL;
2478 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2479 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2480 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2482 ti->level_filename = NULL;
2483 ti->level_filetype = NULL;
2486 ti->first_level = 0;
2488 ti->level_group = FALSE;
2489 ti->handicap_level = 0;
2490 ti->readonly = TRUE;
2491 ti->handicap = TRUE;
2492 ti->skip_levels = FALSE;
2496 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2500 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2502 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2507 /* copy all values from the parent structure */
2509 ti->type = parent->type;
2511 ti->node_top = parent->node_top;
2512 ti->node_parent = parent;
2513 ti->node_group = NULL;
2520 ti->fullpath = NULL;
2521 ti->basepath = NULL;
2522 ti->identifier = NULL;
2523 ti->name = getStringCopy(ANONYMOUS_NAME);
2524 ti->name_sorting = NULL;
2525 ti->author = getStringCopy(parent->author);
2526 ti->year = getStringCopy(parent->year);
2528 ti->sort_priority = parent->sort_priority;
2529 ti->latest_engine = parent->latest_engine;
2530 ti->parent_link = FALSE;
2531 ti->in_user_dir = parent->in_user_dir;
2532 ti->user_defined = parent->user_defined;
2533 ti->color = parent->color;
2534 ti->class_desc = getStringCopy(parent->class_desc);
2536 ti->infotext = getStringCopy(parent->infotext);
2538 if (ti->type == TREE_TYPE_LEVEL_DIR)
2540 ti->imported_from = getStringCopy(parent->imported_from);
2541 ti->imported_by = getStringCopy(parent->imported_by);
2542 ti->tested_by = getStringCopy(parent->tested_by);
2544 ti->graphics_set_ecs = NULL;
2545 ti->graphics_set_aga = NULL;
2546 ti->graphics_set = NULL;
2547 ti->sounds_set = NULL;
2548 ti->music_set = NULL;
2549 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2550 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2551 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2553 ti->level_filename = NULL;
2554 ti->level_filetype = NULL;
2557 ti->first_level = 0;
2559 ti->level_group = FALSE;
2560 ti->handicap_level = 0;
2561 ti->readonly = TRUE;
2562 ti->handicap = TRUE;
2563 ti->skip_levels = FALSE;
2567 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2569 TreeInfo *ti_copy = newTreeInfo();
2571 /* copy all values from the original structure */
2573 ti_copy->type = ti->type;
2575 ti_copy->node_top = ti->node_top;
2576 ti_copy->node_parent = ti->node_parent;
2577 ti_copy->node_group = ti->node_group;
2578 ti_copy->next = ti->next;
2580 ti_copy->cl_first = ti->cl_first;
2581 ti_copy->cl_cursor = ti->cl_cursor;
2583 ti_copy->subdir = getStringCopy(ti->subdir);
2584 ti_copy->fullpath = getStringCopy(ti->fullpath);
2585 ti_copy->basepath = getStringCopy(ti->basepath);
2586 ti_copy->identifier = getStringCopy(ti->identifier);
2587 ti_copy->name = getStringCopy(ti->name);
2588 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2589 ti_copy->author = getStringCopy(ti->author);
2590 ti_copy->year = getStringCopy(ti->year);
2591 ti_copy->imported_from = getStringCopy(ti->imported_from);
2592 ti_copy->imported_by = getStringCopy(ti->imported_by);
2593 ti_copy->tested_by = getStringCopy(ti->tested_by);
2595 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2596 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2597 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2598 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2599 ti_copy->music_set = getStringCopy(ti->music_set);
2600 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2601 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2602 ti_copy->music_path = getStringCopy(ti->music_path);
2604 ti_copy->level_filename = getStringCopy(ti->level_filename);
2605 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2607 ti_copy->levels = ti->levels;
2608 ti_copy->first_level = ti->first_level;
2609 ti_copy->last_level = ti->last_level;
2610 ti_copy->sort_priority = ti->sort_priority;
2612 ti_copy->latest_engine = ti->latest_engine;
2614 ti_copy->level_group = ti->level_group;
2615 ti_copy->parent_link = ti->parent_link;
2616 ti_copy->in_user_dir = ti->in_user_dir;
2617 ti_copy->user_defined = ti->user_defined;
2618 ti_copy->readonly = ti->readonly;
2619 ti_copy->handicap = ti->handicap;
2620 ti_copy->skip_levels = ti->skip_levels;
2622 ti_copy->color = ti->color;
2623 ti_copy->class_desc = getStringCopy(ti->class_desc);
2624 ti_copy->handicap_level = ti->handicap_level;
2626 ti_copy->infotext = getStringCopy(ti->infotext);
2631 static void freeTreeInfo(TreeInfo *ti)
2636 checked_free(ti->subdir);
2637 checked_free(ti->fullpath);
2638 checked_free(ti->basepath);
2639 checked_free(ti->identifier);
2641 checked_free(ti->name);
2642 checked_free(ti->name_sorting);
2643 checked_free(ti->author);
2644 checked_free(ti->year);
2646 checked_free(ti->class_desc);
2648 checked_free(ti->infotext);
2650 if (ti->type == TREE_TYPE_LEVEL_DIR)
2652 checked_free(ti->imported_from);
2653 checked_free(ti->imported_by);
2654 checked_free(ti->tested_by);
2656 checked_free(ti->graphics_set_ecs);
2657 checked_free(ti->graphics_set_aga);
2658 checked_free(ti->graphics_set);
2659 checked_free(ti->sounds_set);
2660 checked_free(ti->music_set);
2662 checked_free(ti->graphics_path);
2663 checked_free(ti->sounds_path);
2664 checked_free(ti->music_path);
2666 checked_free(ti->level_filename);
2667 checked_free(ti->level_filetype);
2673 void setSetupInfo(struct TokenInfo *token_info,
2674 int token_nr, char *token_value)
2676 int token_type = token_info[token_nr].type;
2677 void *setup_value = token_info[token_nr].value;
2679 if (token_value == NULL)
2682 /* set setup field to corresponding token value */
2687 *(boolean *)setup_value = get_boolean_from_string(token_value);
2691 *(int *)setup_value = get_switch3_from_string(token_value);
2695 *(Key *)setup_value = getKeyFromKeyName(token_value);
2699 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2703 *(int *)setup_value = get_integer_from_string(token_value);
2707 checked_free(*(char **)setup_value);
2708 *(char **)setup_value = getStringCopy(token_value);
2716 static int compareTreeInfoEntries(const void *object1, const void *object2)
2718 const TreeInfo *entry1 = *((TreeInfo **)object1);
2719 const TreeInfo *entry2 = *((TreeInfo **)object2);
2720 int class_sorting1, class_sorting2;
2723 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2725 class_sorting1 = LEVELSORTING(entry1);
2726 class_sorting2 = LEVELSORTING(entry2);
2730 class_sorting1 = ARTWORKSORTING(entry1);
2731 class_sorting2 = ARTWORKSORTING(entry2);
2734 if (entry1->parent_link || entry2->parent_link)
2735 compare_result = (entry1->parent_link ? -1 : +1);
2736 else if (entry1->sort_priority == entry2->sort_priority)
2738 char *name1 = getStringToLower(entry1->name_sorting);
2739 char *name2 = getStringToLower(entry2->name_sorting);
2741 compare_result = strcmp(name1, name2);
2746 else if (class_sorting1 == class_sorting2)
2747 compare_result = entry1->sort_priority - entry2->sort_priority;
2749 compare_result = class_sorting1 - class_sorting2;
2751 return compare_result;
2754 static void createParentTreeInfoNode(TreeInfo *node_parent)
2758 if (node_parent == NULL)
2761 ti_new = newTreeInfo();
2762 setTreeInfoToDefaults(ti_new, node_parent->type);
2764 ti_new->node_parent = node_parent;
2765 ti_new->parent_link = TRUE;
2767 setString(&ti_new->identifier, node_parent->identifier);
2768 setString(&ti_new->name, ".. (parent directory)");
2769 setString(&ti_new->name_sorting, ti_new->name);
2771 setString(&ti_new->subdir, "..");
2772 setString(&ti_new->fullpath, node_parent->fullpath);
2774 ti_new->sort_priority = node_parent->sort_priority;
2775 ti_new->latest_engine = node_parent->latest_engine;
2777 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2779 pushTreeInfo(&node_parent->node_group, ti_new);
2783 /* -------------------------------------------------------------------------- */
2784 /* functions for handling level and custom artwork info cache */
2785 /* -------------------------------------------------------------------------- */
2787 static void LoadArtworkInfoCache()
2789 InitCacheDirectory();
2791 if (artworkinfo_cache_old == NULL)
2793 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2795 /* try to load artwork info hash from already existing cache file */
2796 artworkinfo_cache_old = loadSetupFileHash(filename);
2798 /* if no artwork info cache file was found, start with empty hash */
2799 if (artworkinfo_cache_old == NULL)
2800 artworkinfo_cache_old = newSetupFileHash();
2805 if (artworkinfo_cache_new == NULL)
2806 artworkinfo_cache_new = newSetupFileHash();
2809 static void SaveArtworkInfoCache()
2811 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2813 InitCacheDirectory();
2815 saveSetupFileHash(artworkinfo_cache_new, filename);
2820 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2822 static char *prefix = NULL;
2824 checked_free(prefix);
2826 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2831 /* (identical to above function, but separate string buffer needed -- nasty) */
2832 static char *getCacheToken(char *prefix, char *suffix)
2834 static char *token = NULL;
2836 checked_free(token);
2838 token = getStringCat2WithSeparator(prefix, suffix, ".");
2843 static char *getFileTimestamp(char *filename)
2845 struct stat file_status;
2847 if (stat(filename, &file_status) != 0) /* cannot stat file */
2848 return getStringCopy(i_to_a(0));
2850 return getStringCopy(i_to_a(file_status.st_mtime));
2853 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2855 struct stat file_status;
2857 if (timestamp_string == NULL)
2860 if (stat(filename, &file_status) != 0) /* cannot stat file */
2863 return (file_status.st_mtime != atoi(timestamp_string));
2866 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2868 char *identifier = level_node->subdir;
2869 char *type_string = ARTWORK_DIRECTORY(type);
2870 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2871 char *token_main = getCacheToken(token_prefix, "CACHED");
2872 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2873 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2874 TreeInfo *artwork_info = NULL;
2876 if (!use_artworkinfo_cache)
2883 artwork_info = newTreeInfo();
2884 setTreeInfoToDefaults(artwork_info, type);
2886 /* set all structure fields according to the token/value pairs */
2887 ldi = *artwork_info;
2888 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2890 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2891 char *value = getHashEntry(artworkinfo_cache_old, token);
2893 setSetupInfo(artworkinfo_tokens, i, value);
2895 /* check if cache entry for this item is invalid or incomplete */
2899 Error(ERR_WARN, "cache entry '%s' invalid", token);
2906 *artwork_info = ldi;
2911 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2912 LEVELINFO_FILENAME);
2913 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2914 ARTWORKINFO_FILENAME(type));
2916 /* check if corresponding "levelinfo.conf" file has changed */
2917 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2918 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2920 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2923 /* check if corresponding "<artworkinfo>.conf" file has changed */
2924 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2925 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2927 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2932 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2935 checked_free(filename_levelinfo);
2936 checked_free(filename_artworkinfo);
2939 if (!cached && artwork_info != NULL)
2941 freeTreeInfo(artwork_info);
2946 return artwork_info;
2949 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2950 LevelDirTree *level_node, int type)
2952 char *identifier = level_node->subdir;
2953 char *type_string = ARTWORK_DIRECTORY(type);
2954 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2955 char *token_main = getCacheToken(token_prefix, "CACHED");
2956 boolean set_cache_timestamps = TRUE;
2959 setHashEntry(artworkinfo_cache_new, token_main, "true");
2961 if (set_cache_timestamps)
2963 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2964 LEVELINFO_FILENAME);
2965 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2966 ARTWORKINFO_FILENAME(type));
2967 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2968 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2970 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2971 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2973 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2974 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2976 checked_free(filename_levelinfo);
2977 checked_free(filename_artworkinfo);
2978 checked_free(timestamp_levelinfo);
2979 checked_free(timestamp_artworkinfo);
2982 ldi = *artwork_info;
2983 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2985 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2986 char *value = getSetupValue(artworkinfo_tokens[i].type,
2987 artworkinfo_tokens[i].value);
2989 setHashEntry(artworkinfo_cache_new, token, value);
2994 /* -------------------------------------------------------------------------- */
2995 /* functions for loading level info and custom artwork info */
2996 /* -------------------------------------------------------------------------- */
2998 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2999 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3001 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3002 TreeInfo *node_parent,
3003 char *level_directory,
3004 char *directory_name)
3007 static unsigned long progress_delay = 0;
3008 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3010 char *directory_path = getPath2(level_directory, directory_name);
3011 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3012 SetupFileHash *setup_file_hash;
3013 LevelDirTree *leveldir_new = NULL;
3016 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3017 if (!options.debug && !fileExists(filename))
3019 free(directory_path);
3025 setup_file_hash = loadSetupFileHash(filename);
3027 if (setup_file_hash == NULL)
3029 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3031 free(directory_path);
3037 leveldir_new = newTreeInfo();
3040 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3042 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3044 leveldir_new->subdir = getStringCopy(directory_name);
3046 checkSetupFileHashIdentifier(setup_file_hash, filename,
3047 getCookie("LEVELINFO"));
3049 /* set all structure fields according to the token/value pairs */
3050 ldi = *leveldir_new;
3051 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3052 setSetupInfo(levelinfo_tokens, i,
3053 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3054 *leveldir_new = ldi;
3056 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3057 setString(&leveldir_new->name, leveldir_new->subdir);
3059 if (leveldir_new->identifier == NULL)
3060 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3062 if (leveldir_new->name_sorting == NULL)
3063 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3065 if (node_parent == NULL) /* top level group */
3067 leveldir_new->basepath = getStringCopy(level_directory);
3068 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3070 else /* sub level group */
3072 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3073 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3077 if (leveldir_new->levels < 1)
3078 leveldir_new->levels = 1;
3081 leveldir_new->last_level =
3082 leveldir_new->first_level + leveldir_new->levels - 1;
3084 leveldir_new->in_user_dir =
3085 (!strEqual(leveldir_new->basepath, options.level_directory));
3087 /* adjust some settings if user's private level directory was detected */
3088 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3089 leveldir_new->in_user_dir &&
3090 (strEqual(leveldir_new->subdir, getLoginName()) ||
3091 strEqual(leveldir_new->name, getLoginName()) ||
3092 strEqual(leveldir_new->author, getRealName())))
3094 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3095 leveldir_new->readonly = FALSE;
3098 leveldir_new->user_defined =
3099 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3101 leveldir_new->color = LEVELCOLOR(leveldir_new);
3103 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3105 leveldir_new->handicap_level = /* set handicap to default value */
3106 (leveldir_new->user_defined || !leveldir_new->handicap ?
3107 leveldir_new->last_level : leveldir_new->first_level);
3111 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3112 leveldir_new->level_group);
3114 if (leveldir_new->level_group ||
3115 DelayReached(&progress_delay, progress_delay_value))
3116 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3119 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3123 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3125 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3127 /* skip level sets without levels (which are probably artwork base sets) */
3129 freeSetupFileHash(setup_file_hash);
3130 free(directory_path);
3138 pushTreeInfo(node_first, leveldir_new);
3140 freeSetupFileHash(setup_file_hash);
3142 if (leveldir_new->level_group)
3144 /* create node to link back to current level directory */
3145 createParentTreeInfoNode(leveldir_new);
3147 /* recursively step into sub-directory and look for more level series */
3148 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3149 leveldir_new, directory_path);
3152 free(directory_path);
3158 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3159 TreeInfo *node_parent,
3160 char *level_directory)
3163 struct dirent *dir_entry;
3164 boolean valid_entry_found = FALSE;
3166 if ((dir = opendir(level_directory)) == NULL)
3168 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3172 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3174 struct stat file_status;
3175 char *directory_name = dir_entry->d_name;
3176 char *directory_path = getPath2(level_directory, directory_name);
3178 /* skip entries for current and parent directory */
3179 if (strEqual(directory_name, ".") ||
3180 strEqual(directory_name, ".."))
3182 free(directory_path);
3186 /* find out if directory entry is itself a directory */
3187 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3188 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3190 free(directory_path);
3194 free(directory_path);
3196 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3197 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3198 strEqual(directory_name, MUSIC_DIRECTORY))
3201 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3208 /* special case: top level directory may directly contain "levelinfo.conf" */
3209 if (node_parent == NULL && !valid_entry_found)
3211 /* check if this directory directly contains a file "levelinfo.conf" */
3212 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3213 level_directory, ".");
3216 if (!valid_entry_found)
3217 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3221 boolean AdjustGraphicsForEMC()
3223 boolean settings_changed = FALSE;
3225 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3226 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3228 return settings_changed;
3231 void LoadLevelInfo()
3233 InitUserLevelDirectory(getLoginName());
3235 DrawInitText("Loading level series", 120, FC_GREEN);
3237 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3238 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3240 /* after loading all level set information, clone the level directory tree
3241 and remove all level sets without levels (these may still contain artwork
3242 to be offered in the setup menu as "custom artwork", and are therefore
3243 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3244 leveldir_first_all = leveldir_first;
3245 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3247 AdjustGraphicsForEMC();
3249 /* before sorting, the first entries will be from the user directory */
3250 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3252 if (leveldir_first == NULL)
3253 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3255 sortTreeInfo(&leveldir_first);
3258 dumpTreeInfo(leveldir_first, 0);
3262 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3263 TreeInfo *node_parent,
3264 char *base_directory,
3265 char *directory_name, int type)
3267 char *directory_path = getPath2(base_directory, directory_name);
3268 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3269 SetupFileHash *setup_file_hash = NULL;
3270 TreeInfo *artwork_new = NULL;
3273 if (fileExists(filename))
3274 setup_file_hash = loadSetupFileHash(filename);
3276 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3279 struct dirent *dir_entry;
3280 boolean valid_file_found = FALSE;
3282 if ((dir = opendir(directory_path)) != NULL)
3284 while ((dir_entry = readdir(dir)) != NULL)
3286 char *entry_name = dir_entry->d_name;
3288 if (FileIsArtworkType(entry_name, type))
3290 valid_file_found = TRUE;
3298 if (!valid_file_found)
3300 if (!strEqual(directory_name, "."))
3301 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3303 free(directory_path);
3310 artwork_new = newTreeInfo();
3313 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3315 setTreeInfoToDefaults(artwork_new, type);
3317 artwork_new->subdir = getStringCopy(directory_name);
3319 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3322 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3325 /* set all structure fields according to the token/value pairs */
3327 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3328 setSetupInfo(levelinfo_tokens, i,
3329 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3332 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3333 setString(&artwork_new->name, artwork_new->subdir);
3335 if (artwork_new->identifier == NULL)
3336 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3338 if (artwork_new->name_sorting == NULL)
3339 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3342 if (node_parent == NULL) /* top level group */
3344 artwork_new->basepath = getStringCopy(base_directory);
3345 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3347 else /* sub level group */
3349 artwork_new->basepath = getStringCopy(node_parent->basepath);
3350 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3353 artwork_new->in_user_dir =
3354 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3356 /* (may use ".sort_priority" from "setup_file_hash" above) */
3357 artwork_new->color = ARTWORKCOLOR(artwork_new);
3359 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3361 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3363 if (strEqual(artwork_new->subdir, "."))
3365 if (artwork_new->user_defined)
3367 setString(&artwork_new->identifier, "private");
3368 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3372 setString(&artwork_new->identifier, "classic");
3373 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3376 /* set to new values after changing ".sort_priority" */
3377 artwork_new->color = ARTWORKCOLOR(artwork_new);
3379 setString(&artwork_new->class_desc,
3380 getLevelClassDescription(artwork_new));
3384 setString(&artwork_new->identifier, artwork_new->subdir);
3387 setString(&artwork_new->name, artwork_new->identifier);
3388 setString(&artwork_new->name_sorting, artwork_new->name);
3392 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3395 pushTreeInfo(node_first, artwork_new);
3397 freeSetupFileHash(setup_file_hash);
3399 free(directory_path);
3405 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3406 TreeInfo *node_parent,
3407 char *base_directory, int type)
3410 struct dirent *dir_entry;
3411 boolean valid_entry_found = FALSE;
3413 if ((dir = opendir(base_directory)) == NULL)
3415 /* display error if directory is main "options.graphics_directory" etc. */
3416 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3417 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3422 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3424 struct stat file_status;
3425 char *directory_name = dir_entry->d_name;
3426 char *directory_path = getPath2(base_directory, directory_name);
3428 /* skip directory entries for current and parent directory */
3429 if (strEqual(directory_name, ".") ||
3430 strEqual(directory_name, ".."))
3432 free(directory_path);
3436 /* skip directory entries which are not a directory or are not accessible */
3437 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3438 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3440 free(directory_path);
3444 free(directory_path);
3446 /* check if this directory contains artwork with or without config file */
3447 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3449 directory_name, type);
3454 /* check if this directory directly contains artwork itself */
3455 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3456 base_directory, ".",
3458 if (!valid_entry_found)
3459 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3463 static TreeInfo *getDummyArtworkInfo(int type)
3465 /* this is only needed when there is completely no artwork available */
3466 TreeInfo *artwork_new = newTreeInfo();
3468 setTreeInfoToDefaults(artwork_new, type);
3470 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3471 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3472 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3474 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3475 setString(&artwork_new->name, UNDEFINED_FILENAME);
3476 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3481 void LoadArtworkInfo()
3483 LoadArtworkInfoCache();
3485 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3487 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3488 options.graphics_directory,
3489 TREE_TYPE_GRAPHICS_DIR);
3490 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3491 getUserGraphicsDir(),
3492 TREE_TYPE_GRAPHICS_DIR);
3494 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3495 options.sounds_directory,
3496 TREE_TYPE_SOUNDS_DIR);
3497 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3499 TREE_TYPE_SOUNDS_DIR);
3501 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3502 options.music_directory,
3503 TREE_TYPE_MUSIC_DIR);
3504 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3506 TREE_TYPE_MUSIC_DIR);
3508 if (artwork.gfx_first == NULL)
3509 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3510 if (artwork.snd_first == NULL)
3511 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3512 if (artwork.mus_first == NULL)
3513 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3515 /* before sorting, the first entries will be from the user directory */
3516 artwork.gfx_current =
3517 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3518 if (artwork.gfx_current == NULL)
3519 artwork.gfx_current =
3520 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3521 if (artwork.gfx_current == NULL)
3522 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3524 artwork.snd_current =
3525 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3526 if (artwork.snd_current == NULL)
3527 artwork.snd_current =
3528 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3529 if (artwork.snd_current == NULL)
3530 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3532 artwork.mus_current =
3533 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3534 if (artwork.mus_current == NULL)
3535 artwork.mus_current =
3536 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3537 if (artwork.mus_current == NULL)
3538 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3540 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3541 artwork.snd_current_identifier = artwork.snd_current->identifier;
3542 artwork.mus_current_identifier = artwork.mus_current->identifier;
3545 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3546 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3547 printf("music set == %s\n\n", artwork.mus_current_identifier);
3550 sortTreeInfo(&artwork.gfx_first);
3551 sortTreeInfo(&artwork.snd_first);
3552 sortTreeInfo(&artwork.mus_first);
3555 dumpTreeInfo(artwork.gfx_first, 0);
3556 dumpTreeInfo(artwork.snd_first, 0);
3557 dumpTreeInfo(artwork.mus_first, 0);
3561 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3562 LevelDirTree *level_node)
3565 static unsigned long progress_delay = 0;
3566 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3568 int type = (*artwork_node)->type;
3570 /* recursively check all level directories for artwork sub-directories */
3574 /* check all tree entries for artwork, but skip parent link entries */
3575 if (!level_node->parent_link)
3577 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3578 boolean cached = (artwork_new != NULL);
3582 pushTreeInfo(artwork_node, artwork_new);
3586 TreeInfo *topnode_last = *artwork_node;
3587 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3588 ARTWORK_DIRECTORY(type));
3590 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3592 if (topnode_last != *artwork_node) /* check for newly added node */
3594 artwork_new = *artwork_node;
3596 setString(&artwork_new->identifier, level_node->subdir);
3597 setString(&artwork_new->name, level_node->name);
3598 setString(&artwork_new->name_sorting, level_node->name_sorting);
3600 artwork_new->sort_priority = level_node->sort_priority;
3601 artwork_new->color = LEVELCOLOR(artwork_new);
3607 /* insert artwork info (from old cache or filesystem) into new cache */
3608 if (artwork_new != NULL)
3609 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3613 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3614 level_node->level_group);
3616 if (level_node->level_group ||
3617 DelayReached(&progress_delay, progress_delay_value))
3618 DrawInitText(level_node->name, 150, FC_YELLOW);
3621 if (level_node->node_group != NULL)
3622 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3624 level_node = level_node->next;
3628 void LoadLevelArtworkInfo()
3630 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3632 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3633 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3634 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3636 SaveArtworkInfoCache();
3638 /* needed for reloading level artwork not known at ealier stage */
3640 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3642 artwork.gfx_current =
3643 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3644 if (artwork.gfx_current == NULL)
3645 artwork.gfx_current =
3646 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3647 if (artwork.gfx_current == NULL)
3648 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3651 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3653 artwork.snd_current =
3654 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3655 if (artwork.snd_current == NULL)
3656 artwork.snd_current =
3657 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3658 if (artwork.snd_current == NULL)
3659 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3662 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3664 artwork.mus_current =
3665 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3666 if (artwork.mus_current == NULL)
3667 artwork.mus_current =
3668 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3669 if (artwork.mus_current == NULL)
3670 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3673 sortTreeInfo(&artwork.gfx_first);
3674 sortTreeInfo(&artwork.snd_first);
3675 sortTreeInfo(&artwork.mus_first);
3678 dumpTreeInfo(artwork.gfx_first, 0);
3679 dumpTreeInfo(artwork.snd_first, 0);
3680 dumpTreeInfo(artwork.mus_first, 0);
3684 static void SaveUserLevelInfo()
3686 LevelDirTree *level_info;
3691 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3693 if (!(file = fopen(filename, MODE_WRITE)))
3695 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3700 level_info = newTreeInfo();
3702 /* always start with reliable default values */
3703 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3705 setString(&level_info->name, getLoginName());
3706 setString(&level_info->author, getRealName());
3707 level_info->levels = 100;
3708 level_info->first_level = 1;
3710 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3712 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3713 getCookie("LEVELINFO")));
3716 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3718 if (i == LEVELINFO_TOKEN_NAME ||
3719 i == LEVELINFO_TOKEN_AUTHOR ||
3720 i == LEVELINFO_TOKEN_LEVELS ||
3721 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3722 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3724 /* just to make things nicer :) */
3725 if (i == LEVELINFO_TOKEN_AUTHOR)
3726 fprintf(file, "\n");
3729 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3733 SetFilePermissions(filename, PERMS_PRIVATE);
3735 freeTreeInfo(level_info);
3739 char *getSetupValue(int type, void *value)
3741 static char value_string[MAX_LINE_LEN];
3749 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3753 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3757 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3758 *(int *)value == FALSE ? "off" : "on"));
3762 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3765 case TYPE_YES_NO_AUTO:
3766 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3767 *(int *)value == FALSE ? "no" : "yes"));
3771 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3775 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3779 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3783 sprintf(value_string, "%d", *(int *)value);
3787 if (*(char **)value == NULL)
3790 strcpy(value_string, *(char **)value);
3794 value_string[0] = '\0';
3798 if (type & TYPE_GHOSTED)
3799 strcpy(value_string, "n/a");
3801 return value_string;
3804 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3808 static char token_string[MAX_LINE_LEN];
3809 int token_type = token_info[token_nr].type;
3810 void *setup_value = token_info[token_nr].value;
3811 char *token_text = token_info[token_nr].text;
3812 char *value_string = getSetupValue(token_type, setup_value);
3814 /* build complete token string */
3815 sprintf(token_string, "%s%s", prefix, token_text);
3817 /* build setup entry line */
3818 line = getFormattedSetupEntry(token_string, value_string);
3820 if (token_type == TYPE_KEY_X11)
3822 Key key = *(Key *)setup_value;
3823 char *keyname = getKeyNameFromKey(key);
3825 /* add comment, if useful */
3826 if (!strEqual(keyname, "(undefined)") &&
3827 !strEqual(keyname, "(unknown)"))
3829 /* add at least one whitespace */
3831 for (i = strlen(line); i < token_comment_position; i++)
3835 strcat(line, keyname);
3842 void LoadLevelSetup_LastSeries()
3844 /* ----------------------------------------------------------------------- */
3845 /* ~/.<program>/levelsetup.conf */
3846 /* ----------------------------------------------------------------------- */
3848 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3849 SetupFileHash *level_setup_hash = NULL;
3851 /* always start with reliable default values */
3852 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3854 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3855 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3857 if (leveldir_current == NULL)
3858 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3861 if ((level_setup_hash = loadSetupFileHash(filename)))
3863 char *last_level_series =
3864 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3866 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3868 if (leveldir_current == NULL)
3869 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3871 checkSetupFileHashIdentifier(level_setup_hash, filename,
3872 getCookie("LEVELSETUP"));
3874 freeSetupFileHash(level_setup_hash);
3877 Error(ERR_WARN, "using default setup values");
3882 void SaveLevelSetup_LastSeries()
3884 /* ----------------------------------------------------------------------- */
3885 /* ~/.<program>/levelsetup.conf */
3886 /* ----------------------------------------------------------------------- */
3888 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3889 char *level_subdir = leveldir_current->subdir;
3892 InitUserDataDirectory();
3894 if (!(file = fopen(filename, MODE_WRITE)))
3896 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3901 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3902 getCookie("LEVELSETUP")));
3903 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3908 SetFilePermissions(filename, PERMS_PRIVATE);
3913 static void checkSeriesInfo()
3915 static char *level_directory = NULL;
3917 struct dirent *dir_entry;
3919 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3921 level_directory = getPath2((leveldir_current->in_user_dir ?
3922 getUserLevelDir(NULL) :
3923 options.level_directory),
3924 leveldir_current->fullpath);
3926 if ((dir = opendir(level_directory)) == NULL)
3928 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3932 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3934 if (strlen(dir_entry->d_name) > 4 &&
3935 dir_entry->d_name[3] == '.' &&
3936 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3938 char levelnum_str[4];
3941 strncpy(levelnum_str, dir_entry->d_name, 3);
3942 levelnum_str[3] = '\0';
3944 levelnum_value = atoi(levelnum_str);
3947 if (levelnum_value < leveldir_current->first_level)
3949 Error(ERR_WARN, "additional level %d found", levelnum_value);
3950 leveldir_current->first_level = levelnum_value;
3952 else if (levelnum_value > leveldir_current->last_level)
3954 Error(ERR_WARN, "additional level %d found", levelnum_value);
3955 leveldir_current->last_level = levelnum_value;
3964 void LoadLevelSetup_SeriesInfo()
3967 SetupFileHash *level_setup_hash = NULL;
3968 char *level_subdir = leveldir_current->subdir;
3970 /* always start with reliable default values */
3971 level_nr = leveldir_current->first_level;
3973 checkSeriesInfo(leveldir_current);
3975 /* ----------------------------------------------------------------------- */
3976 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3977 /* ----------------------------------------------------------------------- */
3979 level_subdir = leveldir_current->subdir;
3981 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3983 if ((level_setup_hash = loadSetupFileHash(filename)))
3987 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3991 level_nr = atoi(token_value);
3993 if (level_nr < leveldir_current->first_level)
3994 level_nr = leveldir_current->first_level;
3995 if (level_nr > leveldir_current->last_level)
3996 level_nr = leveldir_current->last_level;
3999 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4003 int level_nr = atoi(token_value);
4005 if (level_nr < leveldir_current->first_level)
4006 level_nr = leveldir_current->first_level;
4007 if (level_nr > leveldir_current->last_level + 1)
4008 level_nr = leveldir_current->last_level;
4010 if (leveldir_current->user_defined || !leveldir_current->handicap)
4011 level_nr = leveldir_current->last_level;
4013 leveldir_current->handicap_level = level_nr;
4016 checkSetupFileHashIdentifier(level_setup_hash, filename,
4017 getCookie("LEVELSETUP"));
4019 freeSetupFileHash(level_setup_hash);
4022 Error(ERR_WARN, "using default setup values");
4027 void SaveLevelSetup_SeriesInfo()
4030 char *level_subdir = leveldir_current->subdir;
4031 char *level_nr_str = int2str(level_nr, 0);
4032 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4035 /* ----------------------------------------------------------------------- */
4036 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4037 /* ----------------------------------------------------------------------- */
4039 InitLevelSetupDirectory(level_subdir);
4041 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4043 if (!(file = fopen(filename, MODE_WRITE)))
4045 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4050 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4051 getCookie("LEVELSETUP")));
4052 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4054 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4055 handicap_level_str));
4059 SetFilePermissions(filename, PERMS_PRIVATE);