1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
33 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 char *data_dir = getCommonDataDir();
137 char *score_subdir = SCORES_DIRECTORY;
139 checked_free(score_dir);
141 if (level_subdir != NULL)
142 score_dir = getPath3(data_dir, score_subdir, level_subdir);
144 score_dir = getPath2(data_dir, score_subdir);
149 static char *getLevelSetupDir(char *level_subdir)
151 static char *levelsetup_dir = NULL;
152 char *data_dir = getUserGameDataDir();
153 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
155 checked_free(levelsetup_dir);
157 if (level_subdir != NULL)
158 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
160 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
162 return levelsetup_dir;
165 static char *getCacheDir()
167 static char *cache_dir = NULL;
169 if (cache_dir == NULL)
170 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
175 static char *getLevelDirFromTreeInfo(TreeInfo *node)
177 static char *level_dir = NULL;
180 return options.level_directory;
182 checked_free(level_dir);
184 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
185 options.level_directory), node->fullpath);
190 char *getCurrentLevelDir()
192 return getLevelDirFromTreeInfo(leveldir_current);
195 static char *getTapeDir(char *level_subdir)
197 static char *tape_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *tape_subdir = TAPES_DIRECTORY;
201 checked_free(tape_dir);
203 if (level_subdir != NULL)
204 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
206 tape_dir = getPath2(data_dir, tape_subdir);
211 static char *getSolutionTapeDir()
213 static char *tape_dir = NULL;
214 char *data_dir = getCurrentLevelDir();
215 char *tape_subdir = TAPES_DIRECTORY;
217 checked_free(tape_dir);
219 tape_dir = getPath2(data_dir, tape_subdir);
224 static char *getDefaultGraphicsDir(char *graphics_subdir)
226 static char *graphics_dir = NULL;
228 if (graphics_subdir == NULL)
229 return options.graphics_directory;
231 checked_free(graphics_dir);
233 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
238 static char *getDefaultSoundsDir(char *sounds_subdir)
240 static char *sounds_dir = NULL;
242 if (sounds_subdir == NULL)
243 return options.sounds_directory;
245 checked_free(sounds_dir);
247 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
252 static char *getDefaultMusicDir(char *music_subdir)
254 static char *music_dir = NULL;
256 if (music_subdir == NULL)
257 return options.music_directory;
259 checked_free(music_dir);
261 music_dir = getPath2(options.music_directory, music_subdir);
266 static char *getClassicArtworkSet(int type)
268 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
269 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
270 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
273 static char *getClassicArtworkDir(int type)
275 return (type == TREE_TYPE_GRAPHICS_DIR ?
276 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_SOUNDS_DIR ?
278 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
279 type == TREE_TYPE_MUSIC_DIR ?
280 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
283 static char *getUserGraphicsDir()
285 static char *usergraphics_dir = NULL;
287 if (usergraphics_dir == NULL)
288 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
290 return usergraphics_dir;
293 static char *getUserSoundsDir()
295 static char *usersounds_dir = NULL;
297 if (usersounds_dir == NULL)
298 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
300 return usersounds_dir;
303 static char *getUserMusicDir()
305 static char *usermusic_dir = NULL;
307 if (usermusic_dir == NULL)
308 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
310 return usermusic_dir;
313 static char *getSetupArtworkDir(TreeInfo *ti)
315 static char *artwork_dir = NULL;
320 checked_free(artwork_dir);
322 artwork_dir = getPath2(ti->basepath, ti->fullpath);
327 char *setLevelArtworkDir(TreeInfo *ti)
329 char **artwork_path_ptr, **artwork_set_ptr;
330 TreeInfo *level_artwork;
332 if (ti == NULL || leveldir_current == NULL)
335 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
336 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
338 checked_free(*artwork_path_ptr);
340 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
342 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
347 No (or non-existing) artwork configured in "levelinfo.conf". This would
348 normally result in using the artwork configured in the setup menu. But
349 if an artwork subdirectory exists (which might contain custom artwork
350 or an artwork configuration file), this level artwork must be treated
351 as relative to the default "classic" artwork, not to the artwork that
352 is currently configured in the setup menu.
354 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
355 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
356 the real "classic" artwork from the original R'n'D (like "gfx_classic").
359 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
361 checked_free(*artwork_set_ptr);
363 if (directoryExists(dir))
365 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
366 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
370 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
371 *artwork_set_ptr = NULL;
377 return *artwork_set_ptr;
380 inline static char *getLevelArtworkSet(int type)
382 if (leveldir_current == NULL)
385 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
388 inline static char *getLevelArtworkDir(int type)
390 if (leveldir_current == NULL)
391 return UNDEFINED_FILENAME;
393 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
396 char *getTapeFilename(int nr)
398 static char *filename = NULL;
399 char basename[MAX_FILENAME_LEN];
401 checked_free(filename);
403 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
404 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
409 char *getSolutionTapeFilename(int nr)
411 static char *filename = NULL;
412 char basename[MAX_FILENAME_LEN];
414 checked_free(filename);
416 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
417 filename = getPath2(getSolutionTapeDir(), basename);
419 if (!fileExists(filename))
421 static char *filename_sln = NULL;
423 checked_free(filename_sln);
425 sprintf(basename, "%03d.sln", nr);
426 filename_sln = getPath2(getSolutionTapeDir(), basename);
428 if (fileExists(filename_sln))
435 char *getScoreFilename(int nr)
437 static char *filename = NULL;
438 char basename[MAX_FILENAME_LEN];
440 checked_free(filename);
442 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
443 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
448 char *getSetupFilename()
450 static char *filename = NULL;
452 checked_free(filename);
454 filename = getPath2(getSetupDir(), SETUP_FILENAME);
459 char *getEditorSetupFilename()
461 static char *filename = NULL;
463 checked_free(filename);
464 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
466 if (fileExists(filename))
469 checked_free(filename);
470 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
475 char *getHelpAnimFilename()
477 static char *filename = NULL;
479 checked_free(filename);
481 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
486 char *getHelpTextFilename()
488 static char *filename = NULL;
490 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
497 char *getLevelSetInfoFilename()
499 static char *filename = NULL;
514 for (i = 0; basenames[i] != NULL; i++)
516 checked_free(filename);
517 filename = getPath2(getCurrentLevelDir(), basenames[i]);
519 if (fileExists(filename))
526 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
528 static char basename[32];
530 sprintf(basename, "%s_%d.txt",
531 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
536 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
538 static char *filename = NULL;
540 boolean skip_setup_artwork = FALSE;
542 checked_free(filename);
544 basename = getLevelSetTitleMessageBasename(nr, initial);
546 if (!gfx.override_level_graphics)
548 /* 1st try: look for special artwork in current level series directory */
549 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
550 if (fileExists(filename))
555 /* 2nd try: look for message file in current level set directory */
556 filename = getPath2(getCurrentLevelDir(), basename);
557 if (fileExists(filename))
562 /* check if there is special artwork configured in level series config */
563 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
565 /* 3rd try: look for special artwork configured in level series config */
566 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
567 if (fileExists(filename))
572 /* take missing artwork configured in level set config from default */
573 skip_setup_artwork = TRUE;
577 if (!skip_setup_artwork)
579 /* 4th try: look for special artwork in configured artwork directory */
580 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
581 if (fileExists(filename))
587 /* 5th try: look for default artwork in new default artwork directory */
588 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
589 if (fileExists(filename))
594 /* 6th try: look for default artwork in old default artwork directory */
595 filename = getPath2(options.graphics_directory, basename);
596 if (fileExists(filename))
599 return NULL; /* cannot find specified artwork file anywhere */
602 static char *getCorrectedArtworkBasename(char *basename)
607 char *getCustomImageFilename(char *basename)
609 static char *filename = NULL;
610 boolean skip_setup_artwork = FALSE;
612 checked_free(filename);
614 basename = getCorrectedArtworkBasename(basename);
616 if (!gfx.override_level_graphics)
618 /* 1st try: look for special artwork in current level series directory */
619 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
620 if (fileExists(filename))
625 /* check if there is special artwork configured in level series config */
626 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
628 /* 2nd try: look for special artwork configured in level series config */
629 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
630 if (fileExists(filename))
635 /* take missing artwork configured in level set config from default */
636 skip_setup_artwork = TRUE;
640 if (!skip_setup_artwork)
642 /* 3rd try: look for special artwork in configured artwork directory */
643 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
644 if (fileExists(filename))
650 /* 4th try: look for default artwork in new default artwork directory */
651 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
652 if (fileExists(filename))
657 /* 5th try: look for default artwork in old default artwork directory */
658 filename = getImg2(options.graphics_directory, basename);
659 if (fileExists(filename))
662 #if defined(CREATE_SPECIAL_EDITION)
666 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
668 /* 6th try: look for fallback artwork in old default artwork directory */
669 /* (needed to prevent errors when trying to access unused artwork files) */
670 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
671 if (fileExists(filename))
675 return NULL; /* cannot find specified artwork file anywhere */
678 char *getCustomSoundFilename(char *basename)
680 static char *filename = NULL;
681 boolean skip_setup_artwork = FALSE;
683 checked_free(filename);
685 basename = getCorrectedArtworkBasename(basename);
687 if (!gfx.override_level_sounds)
689 /* 1st try: look for special artwork in current level series directory */
690 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
691 if (fileExists(filename))
696 /* check if there is special artwork configured in level series config */
697 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
699 /* 2nd try: look for special artwork configured in level series config */
700 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
701 if (fileExists(filename))
706 /* take missing artwork configured in level set config from default */
707 skip_setup_artwork = TRUE;
711 if (!skip_setup_artwork)
713 /* 3rd try: look for special artwork in configured artwork directory */
714 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
715 if (fileExists(filename))
721 /* 4th try: look for default artwork in new default artwork directory */
722 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
723 if (fileExists(filename))
728 /* 5th try: look for default artwork in old default artwork directory */
729 filename = getPath2(options.sounds_directory, basename);
730 if (fileExists(filename))
733 #if defined(CREATE_SPECIAL_EDITION)
737 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
739 /* 6th try: look for fallback artwork in old default artwork directory */
740 /* (needed to prevent errors when trying to access unused artwork files) */
741 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
742 if (fileExists(filename))
746 return NULL; /* cannot find specified artwork file anywhere */
749 char *getCustomMusicFilename(char *basename)
751 static char *filename = NULL;
752 boolean skip_setup_artwork = FALSE;
754 checked_free(filename);
756 basename = getCorrectedArtworkBasename(basename);
758 if (!gfx.override_level_music)
760 /* 1st try: look for special artwork in current level series directory */
761 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
762 if (fileExists(filename))
767 /* check if there is special artwork configured in level series config */
768 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
770 /* 2nd try: look for special artwork configured in level series config */
771 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
772 if (fileExists(filename))
777 /* take missing artwork configured in level set config from default */
778 skip_setup_artwork = TRUE;
782 if (!skip_setup_artwork)
784 /* 3rd try: look for special artwork in configured artwork directory */
785 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
786 if (fileExists(filename))
792 /* 4th try: look for default artwork in new default artwork directory */
793 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
794 if (fileExists(filename))
799 /* 5th try: look for default artwork in old default artwork directory */
800 filename = getPath2(options.music_directory, basename);
801 if (fileExists(filename))
804 #if defined(CREATE_SPECIAL_EDITION)
808 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
810 /* 6th try: look for fallback artwork in old default artwork directory */
811 /* (needed to prevent errors when trying to access unused artwork files) */
812 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
813 if (fileExists(filename))
817 return NULL; /* cannot find specified artwork file anywhere */
820 char *getCustomArtworkFilename(char *basename, int type)
822 if (type == ARTWORK_TYPE_GRAPHICS)
823 return getCustomImageFilename(basename);
824 else if (type == ARTWORK_TYPE_SOUNDS)
825 return getCustomSoundFilename(basename);
826 else if (type == ARTWORK_TYPE_MUSIC)
827 return getCustomMusicFilename(basename);
829 return UNDEFINED_FILENAME;
832 char *getCustomArtworkConfigFilename(int type)
834 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
837 char *getCustomArtworkLevelConfigFilename(int type)
839 static char *filename = NULL;
841 checked_free(filename);
843 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
848 char *getCustomMusicDirectory(void)
850 static char *directory = NULL;
851 boolean skip_setup_artwork = FALSE;
853 checked_free(directory);
855 if (!gfx.override_level_music)
857 /* 1st try: look for special artwork in current level series directory */
858 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
859 if (directoryExists(directory))
864 /* check if there is special artwork configured in level series config */
865 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
867 /* 2nd try: look for special artwork configured in level series config */
868 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
869 if (directoryExists(directory))
874 /* take missing artwork configured in level set config from default */
875 skip_setup_artwork = TRUE;
879 if (!skip_setup_artwork)
881 /* 3rd try: look for special artwork in configured artwork directory */
882 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
883 if (directoryExists(directory))
889 /* 4th try: look for default artwork in new default artwork directory */
890 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
891 if (directoryExists(directory))
896 /* 5th try: look for default artwork in old default artwork directory */
897 directory = getStringCopy(options.music_directory);
898 if (directoryExists(directory))
901 return NULL; /* cannot find specified artwork file anywhere */
904 void InitTapeDirectory(char *level_subdir)
906 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
907 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
908 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
911 void InitScoreDirectory(char *level_subdir)
913 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
914 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
915 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
918 static void SaveUserLevelInfo();
920 void InitUserLevelDirectory(char *level_subdir)
922 if (!directoryExists(getUserLevelDir(level_subdir)))
924 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
925 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
926 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
932 void InitLevelSetupDirectory(char *level_subdir)
934 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
935 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
936 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
939 void InitCacheDirectory()
941 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
942 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
946 /* ------------------------------------------------------------------------- */
947 /* some functions to handle lists of level and artwork directories */
948 /* ------------------------------------------------------------------------- */
950 TreeInfo *newTreeInfo()
952 return checked_calloc(sizeof(TreeInfo));
955 TreeInfo *newTreeInfo_setDefaults(int type)
957 TreeInfo *ti = newTreeInfo();
959 setTreeInfoToDefaults(ti, type);
964 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
966 node_new->next = *node_first;
967 *node_first = node_new;
970 int numTreeInfo(TreeInfo *node)
983 boolean validLevelSeries(TreeInfo *node)
985 return (node != NULL && !node->node_group && !node->parent_link);
988 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
993 if (node->node_group) /* enter level group (step down into tree) */
994 return getFirstValidTreeInfoEntry(node->node_group);
995 else if (node->parent_link) /* skip start entry of level group */
997 if (node->next) /* get first real level series entry */
998 return getFirstValidTreeInfoEntry(node->next);
999 else /* leave empty level group and go on */
1000 return getFirstValidTreeInfoEntry(node->node_parent->next);
1002 else /* this seems to be a regular level series */
1006 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1011 if (node->node_parent == NULL) /* top level group */
1012 return *node->node_top;
1013 else /* sub level group */
1014 return node->node_parent->node_group;
1017 int numTreeInfoInGroup(TreeInfo *node)
1019 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1022 int posTreeInfo(TreeInfo *node)
1024 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1029 if (node_cmp == node)
1033 node_cmp = node_cmp->next;
1039 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1041 TreeInfo *node_default = node;
1053 return node_default;
1056 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1058 if (identifier == NULL)
1063 if (node->node_group)
1065 TreeInfo *node_group;
1067 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1072 else if (!node->parent_link)
1074 if (strEqual(identifier, node->identifier))
1084 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1085 TreeInfo *node, boolean skip_sets_without_levels)
1092 if (!node->parent_link && !node->level_group &&
1093 skip_sets_without_levels && node->levels == 0)
1094 return cloneTreeNode(node_top, node_parent, node->next,
1095 skip_sets_without_levels);
1097 node_new = getTreeInfoCopy(node); /* copy complete node */
1099 node_new->node_top = node_top; /* correct top node link */
1100 node_new->node_parent = node_parent; /* correct parent node link */
1102 if (node->level_group)
1103 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1104 skip_sets_without_levels);
1106 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1107 skip_sets_without_levels);
1112 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1114 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1116 *ti_new = ti_cloned;
1119 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1121 boolean settings_changed = FALSE;
1125 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1126 !strEqual(node->graphics_set, node->graphics_set_ecs))
1128 setString(&node->graphics_set, node->graphics_set_ecs);
1129 settings_changed = TRUE;
1131 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1132 !strEqual(node->graphics_set, node->graphics_set_aga))
1134 setString(&node->graphics_set, node->graphics_set_aga);
1135 settings_changed = TRUE;
1138 if (node->node_group != NULL)
1139 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1144 return settings_changed;
1147 void dumpTreeInfo(TreeInfo *node, int depth)
1151 printf("Dumping TreeInfo:\n");
1155 for (i = 0; i < (depth + 1) * 3; i++)
1158 printf("'%s' / '%s'\n", node->identifier, node->name);
1161 // use for dumping artwork info tree
1162 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1163 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1166 if (node->node_group != NULL)
1167 dumpTreeInfo(node->node_group, depth + 1);
1173 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1174 int (*compare_function)(const void *,
1177 int num_nodes = numTreeInfo(*node_first);
1178 TreeInfo **sort_array;
1179 TreeInfo *node = *node_first;
1185 /* allocate array for sorting structure pointers */
1186 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1188 /* writing structure pointers to sorting array */
1189 while (i < num_nodes && node) /* double boundary check... */
1191 sort_array[i] = node;
1197 /* sorting the structure pointers in the sorting array */
1198 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1201 /* update the linkage of list elements with the sorted node array */
1202 for (i = 0; i < num_nodes - 1; i++)
1203 sort_array[i]->next = sort_array[i + 1];
1204 sort_array[num_nodes - 1]->next = NULL;
1206 /* update the linkage of the main list anchor pointer */
1207 *node_first = sort_array[0];
1211 /* now recursively sort the level group structures */
1215 if (node->node_group != NULL)
1216 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1222 void sortTreeInfo(TreeInfo **node_first)
1224 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1228 /* ========================================================================= */
1229 /* some stuff from "files.c" */
1230 /* ========================================================================= */
1232 #if defined(PLATFORM_WIN32)
1234 #define S_IRGRP S_IRUSR
1237 #define S_IROTH S_IRUSR
1240 #define S_IWGRP S_IWUSR
1243 #define S_IWOTH S_IWUSR
1246 #define S_IXGRP S_IXUSR
1249 #define S_IXOTH S_IXUSR
1252 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1257 #endif /* PLATFORM_WIN32 */
1259 /* file permissions for newly written files */
1260 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1261 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1262 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1264 #define MODE_W_PRIVATE (S_IWUSR)
1265 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1266 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1268 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1269 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1271 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1272 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1276 static char *dir = NULL;
1278 #if defined(PLATFORM_WIN32)
1281 dir = checked_malloc(MAX_PATH + 1);
1283 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1286 #elif defined(PLATFORM_UNIX)
1289 if ((dir = getenv("HOME")) == NULL)
1293 if ((pwd = getpwuid(getuid())) != NULL)
1294 dir = getStringCopy(pwd->pw_dir);
1306 char *getCommonDataDir(void)
1308 static char *common_data_dir = NULL;
1310 #if defined(PLATFORM_WIN32)
1311 if (common_data_dir == NULL)
1313 char *dir = checked_malloc(MAX_PATH + 1);
1315 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1316 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1317 common_data_dir = getPath2(dir, program.userdata_subdir);
1319 common_data_dir = options.rw_base_directory;
1322 if (common_data_dir == NULL)
1323 common_data_dir = options.rw_base_directory;
1326 return common_data_dir;
1329 char *getPersonalDataDir(void)
1331 static char *personal_data_dir = NULL;
1333 #if defined(PLATFORM_MACOSX)
1334 if (personal_data_dir == NULL)
1335 personal_data_dir = getPath2(getHomeDir(), "Documents");
1337 if (personal_data_dir == NULL)
1338 personal_data_dir = getHomeDir();
1341 return personal_data_dir;
1344 char *getUserGameDataDir(void)
1346 static char *user_game_data_dir = NULL;
1348 #if defined(PLATFORM_ANDROID)
1349 if (user_game_data_dir == NULL)
1350 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1352 if (user_game_data_dir == NULL)
1353 user_game_data_dir = getPath2(getPersonalDataDir(),
1354 program.userdata_subdir);
1357 return user_game_data_dir;
1360 void updateUserGameDataDir()
1362 #if defined(PLATFORM_MACOSX)
1363 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1364 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1366 /* convert old Unix style game data directory to Mac OS X style, if needed */
1367 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1369 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1371 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1372 userdata_dir_old, userdata_dir_new);
1374 /* continue using Unix style data directory -- this should not happen */
1375 program.userdata_path = getPath2(getPersonalDataDir(),
1376 program.userdata_subdir_unix);
1380 free(userdata_dir_old);
1386 return getUserGameDataDir();
1389 static mode_t posix_umask(mode_t mask)
1391 #if defined(PLATFORM_UNIX)
1398 static int posix_mkdir(const char *pathname, mode_t mode)
1400 #if defined(PLATFORM_WIN32)
1401 return mkdir(pathname);
1403 return mkdir(pathname, mode);
1407 static boolean posix_process_running_setgid()
1409 #if defined(PLATFORM_UNIX)
1410 return (getgid() != getegid());
1416 void createDirectory(char *dir, char *text, int permission_class)
1418 /* leave "other" permissions in umask untouched, but ensure group parts
1419 of USERDATA_DIR_MODE are not masked */
1420 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1421 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1422 mode_t last_umask = posix_umask(0);
1423 mode_t group_umask = ~(dir_mode & S_IRWXG);
1424 int running_setgid = posix_process_running_setgid();
1426 /* if we're setgid, protect files against "other" */
1427 /* else keep umask(0) to make the dir world-writable */
1430 posix_umask(last_umask & group_umask);
1432 dir_mode |= MODE_W_ALL;
1434 if (!directoryExists(dir))
1435 if (posix_mkdir(dir, dir_mode) != 0)
1436 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1437 text, dir, strerror(errno));
1439 if (permission_class == PERMS_PUBLIC && !running_setgid)
1440 chmod(dir, dir_mode);
1442 posix_umask(last_umask); /* restore previous umask */
1445 void InitUserDataDirectory()
1447 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1450 void SetFilePermissions(char *filename, int permission_class)
1452 int running_setgid = posix_process_running_setgid();
1453 int perms = (permission_class == PERMS_PRIVATE ?
1454 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1456 if (permission_class == PERMS_PUBLIC && !running_setgid)
1457 perms |= MODE_W_ALL;
1459 chmod(filename, perms);
1462 char *getCookie(char *file_type)
1464 static char cookie[MAX_COOKIE_LEN + 1];
1466 if (strlen(program.cookie_prefix) + 1 +
1467 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1468 return "[COOKIE ERROR]"; /* should never happen */
1470 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1471 program.cookie_prefix, file_type,
1472 program.version_major, program.version_minor);
1477 void fprintFileHeader(FILE *file, char *basename)
1479 char *prefix = "# ";
1482 fprintf_line_with_prefix(file, prefix, sep1, 77);
1483 fprintf(file, "%s%s\n", prefix, basename);
1484 fprintf_line_with_prefix(file, prefix, sep1, 77);
1485 fprintf(file, "\n");
1488 int getFileVersionFromCookieString(const char *cookie)
1490 const char *ptr_cookie1, *ptr_cookie2;
1491 const char *pattern1 = "_FILE_VERSION_";
1492 const char *pattern2 = "?.?";
1493 const int len_cookie = strlen(cookie);
1494 const int len_pattern1 = strlen(pattern1);
1495 const int len_pattern2 = strlen(pattern2);
1496 const int len_pattern = len_pattern1 + len_pattern2;
1497 int version_major, version_minor;
1499 if (len_cookie <= len_pattern)
1502 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1503 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1505 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1508 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1509 ptr_cookie2[1] != '.' ||
1510 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1513 version_major = ptr_cookie2[0] - '0';
1514 version_minor = ptr_cookie2[2] - '0';
1516 return VERSION_IDENT(version_major, version_minor, 0, 0);
1519 boolean checkCookieString(const char *cookie, const char *template)
1521 const char *pattern = "_FILE_VERSION_?.?";
1522 const int len_cookie = strlen(cookie);
1523 const int len_template = strlen(template);
1524 const int len_pattern = strlen(pattern);
1526 if (len_cookie != len_template)
1529 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1536 /* ------------------------------------------------------------------------- */
1537 /* setup file list and hash handling functions */
1538 /* ------------------------------------------------------------------------- */
1540 char *getFormattedSetupEntry(char *token, char *value)
1543 static char entry[MAX_LINE_LEN];
1545 /* if value is an empty string, just return token without value */
1549 /* start with the token and some spaces to format output line */
1550 sprintf(entry, "%s:", token);
1551 for (i = strlen(entry); i < token_value_position; i++)
1554 /* continue with the token's value */
1555 strcat(entry, value);
1560 SetupFileList *newSetupFileList(char *token, char *value)
1562 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1564 new->token = getStringCopy(token);
1565 new->value = getStringCopy(value);
1572 void freeSetupFileList(SetupFileList *list)
1577 checked_free(list->token);
1578 checked_free(list->value);
1581 freeSetupFileList(list->next);
1586 char *getListEntry(SetupFileList *list, char *token)
1591 if (strEqual(list->token, token))
1594 return getListEntry(list->next, token);
1597 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1602 if (strEqual(list->token, token))
1604 checked_free(list->value);
1606 list->value = getStringCopy(value);
1610 else if (list->next == NULL)
1611 return (list->next = newSetupFileList(token, value));
1613 return setListEntry(list->next, token, value);
1616 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1621 if (list->next == NULL)
1622 return (list->next = newSetupFileList(token, value));
1624 return addListEntry(list->next, token, value);
1627 #if ENABLE_UNUSED_CODE
1629 static void printSetupFileList(SetupFileList *list)
1634 printf("token: '%s'\n", list->token);
1635 printf("value: '%s'\n", list->value);
1637 printSetupFileList(list->next);
1643 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1644 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1645 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1646 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1648 #define insert_hash_entry hashtable_insert
1649 #define search_hash_entry hashtable_search
1650 #define change_hash_entry hashtable_change
1651 #define remove_hash_entry hashtable_remove
1654 unsigned int get_hash_from_key(void *key)
1659 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1660 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1661 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1662 it works better than many other constants, prime or not) has never been
1663 adequately explained.
1665 If you just want to have a good hash function, and cannot wait, djb2
1666 is one of the best string hash functions i know. It has excellent
1667 distribution and speed on many different sets of keys and table sizes.
1668 You are not likely to do better with one of the "well known" functions
1669 such as PJW, K&R, etc.
1671 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1674 char *str = (char *)key;
1675 unsigned int hash = 5381;
1678 while ((c = *str++))
1679 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1684 static int keys_are_equal(void *key1, void *key2)
1686 return (strEqual((char *)key1, (char *)key2));
1689 SetupFileHash *newSetupFileHash()
1691 SetupFileHash *new_hash =
1692 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1694 if (new_hash == NULL)
1695 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1700 void freeSetupFileHash(SetupFileHash *hash)
1705 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1708 char *getHashEntry(SetupFileHash *hash, char *token)
1713 return search_hash_entry(hash, token);
1716 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1723 value_copy = getStringCopy(value);
1725 /* change value; if it does not exist, insert it as new */
1726 if (!change_hash_entry(hash, token, value_copy))
1727 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1728 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1731 char *removeHashEntry(SetupFileHash *hash, char *token)
1736 return remove_hash_entry(hash, token);
1739 #if ENABLE_UNUSED_CODE
1741 static void printSetupFileHash(SetupFileHash *hash)
1743 BEGIN_HASH_ITERATION(hash, itr)
1745 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1746 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1748 END_HASH_ITERATION(hash, itr)
1753 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1754 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1755 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1757 static boolean token_value_separator_found = FALSE;
1758 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1759 static boolean token_value_separator_warning = FALSE;
1761 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1762 static boolean token_already_exists_warning = FALSE;
1765 static boolean getTokenValueFromSetupLineExt(char *line,
1766 char **token_ptr, char **value_ptr,
1767 char *filename, char *line_raw,
1769 boolean separator_required)
1771 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1772 char *token, *value, *line_ptr;
1774 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1775 if (line_raw == NULL)
1777 strncpy(line_copy, line, MAX_LINE_LEN);
1778 line_copy[MAX_LINE_LEN] = '\0';
1781 strcpy(line_raw_copy, line_copy);
1782 line_raw = line_raw_copy;
1785 /* cut trailing comment from input line */
1786 for (line_ptr = line; *line_ptr; line_ptr++)
1788 if (*line_ptr == '#')
1795 /* cut trailing whitespaces from input line */
1796 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1797 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1800 /* ignore empty lines */
1804 /* cut leading whitespaces from token */
1805 for (token = line; *token; token++)
1806 if (*token != ' ' && *token != '\t')
1809 /* start with empty value as reliable default */
1812 token_value_separator_found = FALSE;
1814 /* find end of token to determine start of value */
1815 for (line_ptr = token; *line_ptr; line_ptr++)
1817 /* first look for an explicit token/value separator, like ':' or '=' */
1818 if (*line_ptr == ':' || *line_ptr == '=')
1820 *line_ptr = '\0'; /* terminate token string */
1821 value = line_ptr + 1; /* set beginning of value */
1823 token_value_separator_found = TRUE;
1829 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1830 /* fallback: if no token/value separator found, also allow whitespaces */
1831 if (!token_value_separator_found && !separator_required)
1833 for (line_ptr = token; *line_ptr; line_ptr++)
1835 if (*line_ptr == ' ' || *line_ptr == '\t')
1837 *line_ptr = '\0'; /* terminate token string */
1838 value = line_ptr + 1; /* set beginning of value */
1840 token_value_separator_found = TRUE;
1846 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1847 if (token_value_separator_found)
1849 if (!token_value_separator_warning)
1851 Error(ERR_INFO_LINE, "-");
1853 if (filename != NULL)
1855 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1856 Error(ERR_INFO, "- config file: '%s'", filename);
1860 Error(ERR_WARN, "missing token/value separator(s):");
1863 token_value_separator_warning = TRUE;
1866 if (filename != NULL)
1867 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1869 Error(ERR_INFO, "- line: '%s'", line_raw);
1875 /* cut trailing whitespaces from token */
1876 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1877 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1880 /* cut leading whitespaces from value */
1881 for (; *value; value++)
1882 if (*value != ' ' && *value != '\t')
1891 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1893 /* while the internal (old) interface does not require a token/value
1894 separator (for downwards compatibility with existing files which
1895 don't use them), it is mandatory for the external (new) interface */
1897 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1900 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1901 boolean top_recursion_level, boolean is_hash)
1903 static SetupFileHash *include_filename_hash = NULL;
1904 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1905 char *token, *value, *line_ptr;
1906 void *insert_ptr = NULL;
1907 boolean read_continued_line = FALSE;
1909 int line_nr = 0, token_count = 0, include_count = 0;
1911 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1912 token_value_separator_warning = FALSE;
1915 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1916 token_already_exists_warning = FALSE;
1919 if (!(file = openFile(filename, MODE_READ)))
1921 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1926 /* use "insert pointer" to store list end for constant insertion complexity */
1928 insert_ptr = setup_file_data;
1930 /* on top invocation, create hash to mark included files (to prevent loops) */
1931 if (top_recursion_level)
1932 include_filename_hash = newSetupFileHash();
1934 /* mark this file as already included (to prevent including it again) */
1935 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1937 while (!checkEndOfFile(file))
1939 /* read next line of input file */
1940 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1943 /* check if line was completely read and is terminated by line break */
1944 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1947 /* cut trailing line break (this can be newline and/or carriage return) */
1948 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1949 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1952 /* copy raw input line for later use (mainly debugging output) */
1953 strcpy(line_raw, line);
1955 if (read_continued_line)
1957 /* append new line to existing line, if there is enough space */
1958 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1959 strcat(previous_line, line_ptr);
1961 strcpy(line, previous_line); /* copy storage buffer to line */
1963 read_continued_line = FALSE;
1966 /* if the last character is '\', continue at next line */
1967 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1969 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1970 strcpy(previous_line, line); /* copy line to storage buffer */
1972 read_continued_line = TRUE;
1977 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1978 line_raw, line_nr, FALSE))
1983 if (strEqual(token, "include"))
1985 if (getHashEntry(include_filename_hash, value) == NULL)
1987 char *basepath = getBasePath(filename);
1988 char *basename = getBaseName(value);
1989 char *filename_include = getPath2(basepath, basename);
1991 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1995 free(filename_include);
2001 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2008 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2010 getHashEntry((SetupFileHash *)setup_file_data, token);
2012 if (old_value != NULL)
2014 if (!token_already_exists_warning)
2016 Error(ERR_INFO_LINE, "-");
2017 Error(ERR_WARN, "duplicate token(s) found in config file:");
2018 Error(ERR_INFO, "- config file: '%s'", filename);
2020 token_already_exists_warning = TRUE;
2023 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2024 Error(ERR_INFO, " old value: '%s'", old_value);
2025 Error(ERR_INFO, " new value: '%s'", value);
2029 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2033 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2043 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2044 if (token_value_separator_warning)
2045 Error(ERR_INFO_LINE, "-");
2048 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2049 if (token_already_exists_warning)
2050 Error(ERR_INFO_LINE, "-");
2053 if (token_count == 0 && include_count == 0)
2054 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2056 if (top_recursion_level)
2057 freeSetupFileHash(include_filename_hash);
2062 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2066 if (!(file = fopen(filename, MODE_WRITE)))
2068 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2073 BEGIN_HASH_ITERATION(hash, itr)
2075 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2076 HASH_ITERATION_VALUE(itr)));
2078 END_HASH_ITERATION(hash, itr)
2083 SetupFileList *loadSetupFileList(char *filename)
2085 SetupFileList *setup_file_list = newSetupFileList("", "");
2086 SetupFileList *first_valid_list_entry;
2088 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2090 freeSetupFileList(setup_file_list);
2095 first_valid_list_entry = setup_file_list->next;
2097 /* free empty list header */
2098 setup_file_list->next = NULL;
2099 freeSetupFileList(setup_file_list);
2101 return first_valid_list_entry;
2104 SetupFileHash *loadSetupFileHash(char *filename)
2106 SetupFileHash *setup_file_hash = newSetupFileHash();
2108 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2110 freeSetupFileHash(setup_file_hash);
2115 return setup_file_hash;
2119 /* ========================================================================= */
2120 /* setup file stuff */
2121 /* ========================================================================= */
2123 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2124 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2125 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2127 /* level directory info */
2128 #define LEVELINFO_TOKEN_IDENTIFIER 0
2129 #define LEVELINFO_TOKEN_NAME 1
2130 #define LEVELINFO_TOKEN_NAME_SORTING 2
2131 #define LEVELINFO_TOKEN_AUTHOR 3
2132 #define LEVELINFO_TOKEN_YEAR 4
2133 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2134 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2135 #define LEVELINFO_TOKEN_TESTED_BY 7
2136 #define LEVELINFO_TOKEN_LEVELS 8
2137 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2138 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2139 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2140 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2141 #define LEVELINFO_TOKEN_READONLY 13
2142 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2143 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2144 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2145 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2146 #define LEVELINFO_TOKEN_MUSIC_SET 18
2147 #define LEVELINFO_TOKEN_FILENAME 19
2148 #define LEVELINFO_TOKEN_FILETYPE 20
2149 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2150 #define LEVELINFO_TOKEN_HANDICAP 22
2151 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2153 #define NUM_LEVELINFO_TOKENS 24
2155 static LevelDirTree ldi;
2157 static struct TokenInfo levelinfo_tokens[] =
2159 /* level directory info */
2160 { TYPE_STRING, &ldi.identifier, "identifier" },
2161 { TYPE_STRING, &ldi.name, "name" },
2162 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2163 { TYPE_STRING, &ldi.author, "author" },
2164 { TYPE_STRING, &ldi.year, "year" },
2165 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2166 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2167 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2168 { TYPE_INTEGER, &ldi.levels, "levels" },
2169 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2170 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2171 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2172 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2173 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2174 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2175 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2176 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2177 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2178 { TYPE_STRING, &ldi.music_set, "music_set" },
2179 { TYPE_STRING, &ldi.level_filename, "filename" },
2180 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2181 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2182 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2183 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2186 static struct TokenInfo artworkinfo_tokens[] =
2188 /* artwork directory info */
2189 { TYPE_STRING, &ldi.identifier, "identifier" },
2190 { TYPE_STRING, &ldi.subdir, "subdir" },
2191 { TYPE_STRING, &ldi.name, "name" },
2192 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2193 { TYPE_STRING, &ldi.author, "author" },
2194 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2195 { TYPE_STRING, &ldi.basepath, "basepath" },
2196 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2197 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2198 { TYPE_INTEGER, &ldi.color, "color" },
2199 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2204 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2208 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2209 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2210 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2211 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2214 ti->node_parent = NULL;
2215 ti->node_group = NULL;
2222 ti->fullpath = NULL;
2223 ti->basepath = NULL;
2224 ti->identifier = NULL;
2225 ti->name = getStringCopy(ANONYMOUS_NAME);
2226 ti->name_sorting = NULL;
2227 ti->author = getStringCopy(ANONYMOUS_NAME);
2230 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2231 ti->latest_engine = FALSE; /* default: get from level */
2232 ti->parent_link = FALSE;
2233 ti->in_user_dir = FALSE;
2234 ti->user_defined = FALSE;
2236 ti->class_desc = NULL;
2238 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2240 if (ti->type == TREE_TYPE_LEVEL_DIR)
2242 ti->imported_from = NULL;
2243 ti->imported_by = NULL;
2244 ti->tested_by = NULL;
2246 ti->graphics_set_ecs = NULL;
2247 ti->graphics_set_aga = NULL;
2248 ti->graphics_set = NULL;
2249 ti->sounds_set = NULL;
2250 ti->music_set = NULL;
2251 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2252 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2253 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2255 ti->level_filename = NULL;
2256 ti->level_filetype = NULL;
2258 ti->special_flags = NULL;
2261 ti->first_level = 0;
2263 ti->level_group = FALSE;
2264 ti->handicap_level = 0;
2265 ti->readonly = TRUE;
2266 ti->handicap = TRUE;
2267 ti->skip_levels = FALSE;
2271 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2275 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2277 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2282 /* copy all values from the parent structure */
2284 ti->type = parent->type;
2286 ti->node_top = parent->node_top;
2287 ti->node_parent = parent;
2288 ti->node_group = NULL;
2295 ti->fullpath = NULL;
2296 ti->basepath = NULL;
2297 ti->identifier = NULL;
2298 ti->name = getStringCopy(ANONYMOUS_NAME);
2299 ti->name_sorting = NULL;
2300 ti->author = getStringCopy(parent->author);
2301 ti->year = getStringCopy(parent->year);
2303 ti->sort_priority = parent->sort_priority;
2304 ti->latest_engine = parent->latest_engine;
2305 ti->parent_link = FALSE;
2306 ti->in_user_dir = parent->in_user_dir;
2307 ti->user_defined = parent->user_defined;
2308 ti->color = parent->color;
2309 ti->class_desc = getStringCopy(parent->class_desc);
2311 ti->infotext = getStringCopy(parent->infotext);
2313 if (ti->type == TREE_TYPE_LEVEL_DIR)
2315 ti->imported_from = getStringCopy(parent->imported_from);
2316 ti->imported_by = getStringCopy(parent->imported_by);
2317 ti->tested_by = getStringCopy(parent->tested_by);
2319 ti->graphics_set_ecs = NULL;
2320 ti->graphics_set_aga = NULL;
2321 ti->graphics_set = NULL;
2322 ti->sounds_set = NULL;
2323 ti->music_set = NULL;
2324 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2325 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2326 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2328 ti->level_filename = NULL;
2329 ti->level_filetype = NULL;
2331 ti->special_flags = getStringCopy(parent->special_flags);
2334 ti->first_level = 0;
2336 ti->level_group = FALSE;
2337 ti->handicap_level = 0;
2338 ti->readonly = parent->readonly;
2339 ti->handicap = TRUE;
2340 ti->skip_levels = FALSE;
2344 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2346 TreeInfo *ti_copy = newTreeInfo();
2348 /* copy all values from the original structure */
2350 ti_copy->type = ti->type;
2352 ti_copy->node_top = ti->node_top;
2353 ti_copy->node_parent = ti->node_parent;
2354 ti_copy->node_group = ti->node_group;
2355 ti_copy->next = ti->next;
2357 ti_copy->cl_first = ti->cl_first;
2358 ti_copy->cl_cursor = ti->cl_cursor;
2360 ti_copy->subdir = getStringCopy(ti->subdir);
2361 ti_copy->fullpath = getStringCopy(ti->fullpath);
2362 ti_copy->basepath = getStringCopy(ti->basepath);
2363 ti_copy->identifier = getStringCopy(ti->identifier);
2364 ti_copy->name = getStringCopy(ti->name);
2365 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2366 ti_copy->author = getStringCopy(ti->author);
2367 ti_copy->year = getStringCopy(ti->year);
2368 ti_copy->imported_from = getStringCopy(ti->imported_from);
2369 ti_copy->imported_by = getStringCopy(ti->imported_by);
2370 ti_copy->tested_by = getStringCopy(ti->tested_by);
2372 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2373 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2374 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2375 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2376 ti_copy->music_set = getStringCopy(ti->music_set);
2377 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2378 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2379 ti_copy->music_path = getStringCopy(ti->music_path);
2381 ti_copy->level_filename = getStringCopy(ti->level_filename);
2382 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2384 ti_copy->special_flags = getStringCopy(ti->special_flags);
2386 ti_copy->levels = ti->levels;
2387 ti_copy->first_level = ti->first_level;
2388 ti_copy->last_level = ti->last_level;
2389 ti_copy->sort_priority = ti->sort_priority;
2391 ti_copy->latest_engine = ti->latest_engine;
2393 ti_copy->level_group = ti->level_group;
2394 ti_copy->parent_link = ti->parent_link;
2395 ti_copy->in_user_dir = ti->in_user_dir;
2396 ti_copy->user_defined = ti->user_defined;
2397 ti_copy->readonly = ti->readonly;
2398 ti_copy->handicap = ti->handicap;
2399 ti_copy->skip_levels = ti->skip_levels;
2401 ti_copy->color = ti->color;
2402 ti_copy->class_desc = getStringCopy(ti->class_desc);
2403 ti_copy->handicap_level = ti->handicap_level;
2405 ti_copy->infotext = getStringCopy(ti->infotext);
2410 void freeTreeInfo(TreeInfo *ti)
2415 checked_free(ti->subdir);
2416 checked_free(ti->fullpath);
2417 checked_free(ti->basepath);
2418 checked_free(ti->identifier);
2420 checked_free(ti->name);
2421 checked_free(ti->name_sorting);
2422 checked_free(ti->author);
2423 checked_free(ti->year);
2425 checked_free(ti->class_desc);
2427 checked_free(ti->infotext);
2429 if (ti->type == TREE_TYPE_LEVEL_DIR)
2431 checked_free(ti->imported_from);
2432 checked_free(ti->imported_by);
2433 checked_free(ti->tested_by);
2435 checked_free(ti->graphics_set_ecs);
2436 checked_free(ti->graphics_set_aga);
2437 checked_free(ti->graphics_set);
2438 checked_free(ti->sounds_set);
2439 checked_free(ti->music_set);
2441 checked_free(ti->graphics_path);
2442 checked_free(ti->sounds_path);
2443 checked_free(ti->music_path);
2445 checked_free(ti->level_filename);
2446 checked_free(ti->level_filetype);
2448 checked_free(ti->special_flags);
2451 // recursively free child node
2453 freeTreeInfo(ti->node_group);
2455 // recursively free next node
2457 freeTreeInfo(ti->next);
2462 void setSetupInfo(struct TokenInfo *token_info,
2463 int token_nr, char *token_value)
2465 int token_type = token_info[token_nr].type;
2466 void *setup_value = token_info[token_nr].value;
2468 if (token_value == NULL)
2471 /* set setup field to corresponding token value */
2476 *(boolean *)setup_value = get_boolean_from_string(token_value);
2480 *(int *)setup_value = get_switch3_from_string(token_value);
2484 *(Key *)setup_value = getKeyFromKeyName(token_value);
2488 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2492 *(int *)setup_value = get_integer_from_string(token_value);
2496 checked_free(*(char **)setup_value);
2497 *(char **)setup_value = getStringCopy(token_value);
2505 static int compareTreeInfoEntries(const void *object1, const void *object2)
2507 const TreeInfo *entry1 = *((TreeInfo **)object1);
2508 const TreeInfo *entry2 = *((TreeInfo **)object2);
2509 int class_sorting1 = 0, class_sorting2 = 0;
2512 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2514 class_sorting1 = LEVELSORTING(entry1);
2515 class_sorting2 = LEVELSORTING(entry2);
2517 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2518 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2519 entry1->type == TREE_TYPE_MUSIC_DIR)
2521 class_sorting1 = ARTWORKSORTING(entry1);
2522 class_sorting2 = ARTWORKSORTING(entry2);
2525 if (entry1->parent_link || entry2->parent_link)
2526 compare_result = (entry1->parent_link ? -1 : +1);
2527 else if (entry1->sort_priority == entry2->sort_priority)
2529 char *name1 = getStringToLower(entry1->name_sorting);
2530 char *name2 = getStringToLower(entry2->name_sorting);
2532 compare_result = strcmp(name1, name2);
2537 else if (class_sorting1 == class_sorting2)
2538 compare_result = entry1->sort_priority - entry2->sort_priority;
2540 compare_result = class_sorting1 - class_sorting2;
2542 return compare_result;
2545 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2549 if (node_parent == NULL)
2552 ti_new = newTreeInfo();
2553 setTreeInfoToDefaults(ti_new, node_parent->type);
2555 ti_new->node_parent = node_parent;
2556 ti_new->parent_link = TRUE;
2558 setString(&ti_new->identifier, node_parent->identifier);
2559 setString(&ti_new->name, ".. (parent directory)");
2560 setString(&ti_new->name_sorting, ti_new->name);
2562 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2563 setString(&ti_new->fullpath, node_parent->fullpath);
2565 ti_new->sort_priority = node_parent->sort_priority;
2566 ti_new->latest_engine = node_parent->latest_engine;
2568 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2570 pushTreeInfo(&node_parent->node_group, ti_new);
2575 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2577 TreeInfo *ti_new, *ti_new2;
2579 if (node_first == NULL)
2582 ti_new = newTreeInfo();
2583 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2585 ti_new->node_parent = NULL;
2586 ti_new->parent_link = FALSE;
2588 setString(&ti_new->identifier, node_first->identifier);
2589 setString(&ti_new->name, "level sets");
2590 setString(&ti_new->name_sorting, ti_new->name);
2592 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2593 setString(&ti_new->fullpath, node_first->fullpath);
2595 ti_new->sort_priority = node_first->sort_priority;;
2596 ti_new->latest_engine = node_first->latest_engine;
2598 setString(&ti_new->class_desc, "level sets");
2600 ti_new->node_group = node_first;
2601 ti_new->level_group = TRUE;
2603 ti_new2 = createParentTreeInfoNode(ti_new);
2605 setString(&ti_new2->name, ".. (main menu)");
2606 setString(&ti_new2->name_sorting, ti_new2->name);
2612 /* -------------------------------------------------------------------------- */
2613 /* functions for handling level and custom artwork info cache */
2614 /* -------------------------------------------------------------------------- */
2616 static void LoadArtworkInfoCache()
2618 InitCacheDirectory();
2620 if (artworkinfo_cache_old == NULL)
2622 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2624 /* try to load artwork info hash from already existing cache file */
2625 artworkinfo_cache_old = loadSetupFileHash(filename);
2627 /* if no artwork info cache file was found, start with empty hash */
2628 if (artworkinfo_cache_old == NULL)
2629 artworkinfo_cache_old = newSetupFileHash();
2634 if (artworkinfo_cache_new == NULL)
2635 artworkinfo_cache_new = newSetupFileHash();
2638 static void SaveArtworkInfoCache()
2640 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2642 InitCacheDirectory();
2644 saveSetupFileHash(artworkinfo_cache_new, filename);
2649 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2651 static char *prefix = NULL;
2653 checked_free(prefix);
2655 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2660 /* (identical to above function, but separate string buffer needed -- nasty) */
2661 static char *getCacheToken(char *prefix, char *suffix)
2663 static char *token = NULL;
2665 checked_free(token);
2667 token = getStringCat2WithSeparator(prefix, suffix, ".");
2672 static char *getFileTimestampString(char *filename)
2674 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2677 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2679 struct stat file_status;
2681 if (timestamp_string == NULL)
2684 if (stat(filename, &file_status) != 0) /* cannot stat file */
2687 return (file_status.st_mtime != atoi(timestamp_string));
2690 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2692 char *identifier = level_node->subdir;
2693 char *type_string = ARTWORK_DIRECTORY(type);
2694 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2695 char *token_main = getCacheToken(token_prefix, "CACHED");
2696 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2697 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2698 TreeInfo *artwork_info = NULL;
2700 if (!use_artworkinfo_cache)
2707 artwork_info = newTreeInfo();
2708 setTreeInfoToDefaults(artwork_info, type);
2710 /* set all structure fields according to the token/value pairs */
2711 ldi = *artwork_info;
2712 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2714 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2715 char *value = getHashEntry(artworkinfo_cache_old, token);
2717 setSetupInfo(artworkinfo_tokens, i, value);
2719 /* check if cache entry for this item is invalid or incomplete */
2722 Error(ERR_WARN, "cache entry '%s' invalid", token);
2728 *artwork_info = ldi;
2733 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2734 LEVELINFO_FILENAME);
2735 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2736 ARTWORKINFO_FILENAME(type));
2738 /* check if corresponding "levelinfo.conf" file has changed */
2739 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2740 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2742 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2745 /* check if corresponding "<artworkinfo>.conf" file has changed */
2746 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2747 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2749 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2752 checked_free(filename_levelinfo);
2753 checked_free(filename_artworkinfo);
2756 if (!cached && artwork_info != NULL)
2758 freeTreeInfo(artwork_info);
2763 return artwork_info;
2766 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2767 LevelDirTree *level_node, int type)
2769 char *identifier = level_node->subdir;
2770 char *type_string = ARTWORK_DIRECTORY(type);
2771 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2772 char *token_main = getCacheToken(token_prefix, "CACHED");
2773 boolean set_cache_timestamps = TRUE;
2776 setHashEntry(artworkinfo_cache_new, token_main, "true");
2778 if (set_cache_timestamps)
2780 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2781 LEVELINFO_FILENAME);
2782 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2783 ARTWORKINFO_FILENAME(type));
2784 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2785 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2787 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2788 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2790 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2791 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2793 checked_free(filename_levelinfo);
2794 checked_free(filename_artworkinfo);
2795 checked_free(timestamp_levelinfo);
2796 checked_free(timestamp_artworkinfo);
2799 ldi = *artwork_info;
2800 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2802 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2803 char *value = getSetupValue(artworkinfo_tokens[i].type,
2804 artworkinfo_tokens[i].value);
2806 setHashEntry(artworkinfo_cache_new, token, value);
2811 /* -------------------------------------------------------------------------- */
2812 /* functions for loading level info and custom artwork info */
2813 /* -------------------------------------------------------------------------- */
2815 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2816 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2818 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2819 TreeInfo *node_parent,
2820 char *level_directory,
2821 char *directory_name)
2823 char *directory_path = getPath2(level_directory, directory_name);
2824 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2825 SetupFileHash *setup_file_hash;
2826 LevelDirTree *leveldir_new = NULL;
2829 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2830 if (!options.debug && !fileExists(filename))
2832 free(directory_path);
2838 setup_file_hash = loadSetupFileHash(filename);
2840 if (setup_file_hash == NULL)
2842 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2844 free(directory_path);
2850 leveldir_new = newTreeInfo();
2853 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2855 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2857 leveldir_new->subdir = getStringCopy(directory_name);
2859 /* set all structure fields according to the token/value pairs */
2860 ldi = *leveldir_new;
2861 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2862 setSetupInfo(levelinfo_tokens, i,
2863 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2864 *leveldir_new = ldi;
2866 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2867 setString(&leveldir_new->name, leveldir_new->subdir);
2869 if (leveldir_new->identifier == NULL)
2870 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2872 if (leveldir_new->name_sorting == NULL)
2873 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2875 if (node_parent == NULL) /* top level group */
2877 leveldir_new->basepath = getStringCopy(level_directory);
2878 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2880 else /* sub level group */
2882 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2883 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2886 leveldir_new->last_level =
2887 leveldir_new->first_level + leveldir_new->levels - 1;
2889 leveldir_new->in_user_dir =
2890 (!strEqual(leveldir_new->basepath, options.level_directory));
2892 /* adjust some settings if user's private level directory was detected */
2893 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2894 leveldir_new->in_user_dir &&
2895 (strEqual(leveldir_new->subdir, getLoginName()) ||
2896 strEqual(leveldir_new->name, getLoginName()) ||
2897 strEqual(leveldir_new->author, getRealName())))
2899 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2900 leveldir_new->readonly = FALSE;
2903 leveldir_new->user_defined =
2904 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2906 leveldir_new->color = LEVELCOLOR(leveldir_new);
2908 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2910 leveldir_new->handicap_level = /* set handicap to default value */
2911 (leveldir_new->user_defined || !leveldir_new->handicap ?
2912 leveldir_new->last_level : leveldir_new->first_level);
2914 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2916 pushTreeInfo(node_first, leveldir_new);
2918 freeSetupFileHash(setup_file_hash);
2920 if (leveldir_new->level_group)
2922 /* create node to link back to current level directory */
2923 createParentTreeInfoNode(leveldir_new);
2925 /* recursively step into sub-directory and look for more level series */
2926 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2927 leveldir_new, directory_path);
2930 free(directory_path);
2936 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2937 TreeInfo *node_parent,
2938 char *level_directory)
2941 DirectoryEntry *dir_entry;
2942 boolean valid_entry_found = FALSE;
2944 if ((dir = openDirectory(level_directory)) == NULL)
2946 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2951 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2953 char *directory_name = dir_entry->basename;
2954 char *directory_path = getPath2(level_directory, directory_name);
2956 /* skip entries for current and parent directory */
2957 if (strEqual(directory_name, ".") ||
2958 strEqual(directory_name, ".."))
2960 free(directory_path);
2965 /* find out if directory entry is itself a directory */
2966 if (!dir_entry->is_directory) /* not a directory */
2968 free(directory_path);
2973 free(directory_path);
2975 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2976 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2977 strEqual(directory_name, MUSIC_DIRECTORY))
2980 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2985 closeDirectory(dir);
2987 /* special case: top level directory may directly contain "levelinfo.conf" */
2988 if (node_parent == NULL && !valid_entry_found)
2990 /* check if this directory directly contains a file "levelinfo.conf" */
2991 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2992 level_directory, ".");
2995 if (!valid_entry_found)
2996 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3000 boolean AdjustGraphicsForEMC()
3002 boolean settings_changed = FALSE;
3004 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3005 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3007 return settings_changed;
3010 void LoadLevelInfo()
3012 InitUserLevelDirectory(getLoginName());
3014 DrawInitText("Loading level series", 120, FC_GREEN);
3016 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3017 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3019 leveldir_first = createTopTreeInfoNode(leveldir_first);
3021 /* after loading all level set information, clone the level directory tree
3022 and remove all level sets without levels (these may still contain artwork
3023 to be offered in the setup menu as "custom artwork", and are therefore
3024 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3025 leveldir_first_all = leveldir_first;
3026 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3028 AdjustGraphicsForEMC();
3030 /* before sorting, the first entries will be from the user directory */
3031 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3033 if (leveldir_first == NULL)
3034 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3036 sortTreeInfo(&leveldir_first);
3038 #if ENABLE_UNUSED_CODE
3039 dumpTreeInfo(leveldir_first, 0);
3043 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3044 TreeInfo *node_parent,
3045 char *base_directory,
3046 char *directory_name, int type)
3048 char *directory_path = getPath2(base_directory, directory_name);
3049 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3050 SetupFileHash *setup_file_hash = NULL;
3051 TreeInfo *artwork_new = NULL;
3054 if (fileExists(filename))
3055 setup_file_hash = loadSetupFileHash(filename);
3057 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3060 DirectoryEntry *dir_entry;
3061 boolean valid_file_found = FALSE;
3063 if ((dir = openDirectory(directory_path)) != NULL)
3065 while ((dir_entry = readDirectory(dir)) != NULL)
3067 if (FileIsArtworkType(dir_entry->filename, type))
3069 valid_file_found = TRUE;
3075 closeDirectory(dir);
3078 if (!valid_file_found)
3080 if (!strEqual(directory_name, "."))
3081 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3083 free(directory_path);
3090 artwork_new = newTreeInfo();
3093 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3095 setTreeInfoToDefaults(artwork_new, type);
3097 artwork_new->subdir = getStringCopy(directory_name);
3099 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3101 /* set all structure fields according to the token/value pairs */
3103 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3104 setSetupInfo(levelinfo_tokens, i,
3105 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3108 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3109 setString(&artwork_new->name, artwork_new->subdir);
3111 if (artwork_new->identifier == NULL)
3112 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3114 if (artwork_new->name_sorting == NULL)
3115 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3118 if (node_parent == NULL) /* top level group */
3120 artwork_new->basepath = getStringCopy(base_directory);
3121 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3123 else /* sub level group */
3125 artwork_new->basepath = getStringCopy(node_parent->basepath);
3126 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3129 artwork_new->in_user_dir =
3130 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3132 /* (may use ".sort_priority" from "setup_file_hash" above) */
3133 artwork_new->color = ARTWORKCOLOR(artwork_new);
3135 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3137 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3139 if (strEqual(artwork_new->subdir, "."))
3141 if (artwork_new->user_defined)
3143 setString(&artwork_new->identifier, "private");
3144 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3148 setString(&artwork_new->identifier, "classic");
3149 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3152 /* set to new values after changing ".sort_priority" */
3153 artwork_new->color = ARTWORKCOLOR(artwork_new);
3155 setString(&artwork_new->class_desc,
3156 getLevelClassDescription(artwork_new));
3160 setString(&artwork_new->identifier, artwork_new->subdir);
3163 setString(&artwork_new->name, artwork_new->identifier);
3164 setString(&artwork_new->name_sorting, artwork_new->name);
3167 pushTreeInfo(node_first, artwork_new);
3169 freeSetupFileHash(setup_file_hash);
3171 free(directory_path);
3177 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3178 TreeInfo *node_parent,
3179 char *base_directory, int type)
3182 DirectoryEntry *dir_entry;
3183 boolean valid_entry_found = FALSE;
3185 if ((dir = openDirectory(base_directory)) == NULL)
3187 /* display error if directory is main "options.graphics_directory" etc. */
3188 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3189 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3194 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3196 char *directory_name = dir_entry->basename;
3197 char *directory_path = getPath2(base_directory, directory_name);
3199 /* skip directory entries for current and parent directory */
3200 if (strEqual(directory_name, ".") ||
3201 strEqual(directory_name, ".."))
3203 free(directory_path);
3208 /* skip directory entries which are not a directory */
3209 if (!dir_entry->is_directory) /* not a directory */
3211 free(directory_path);
3216 free(directory_path);
3218 /* check if this directory contains artwork with or without config file */
3219 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3221 directory_name, type);
3224 closeDirectory(dir);
3226 /* check if this directory directly contains artwork itself */
3227 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3228 base_directory, ".",
3230 if (!valid_entry_found)
3231 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3235 static TreeInfo *getDummyArtworkInfo(int type)
3237 /* this is only needed when there is completely no artwork available */
3238 TreeInfo *artwork_new = newTreeInfo();
3240 setTreeInfoToDefaults(artwork_new, type);
3242 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3243 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3244 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3246 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3247 setString(&artwork_new->name, UNDEFINED_FILENAME);
3248 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3253 void LoadArtworkInfo()
3255 LoadArtworkInfoCache();
3257 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3259 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3260 options.graphics_directory,
3261 TREE_TYPE_GRAPHICS_DIR);
3262 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3263 getUserGraphicsDir(),
3264 TREE_TYPE_GRAPHICS_DIR);
3266 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3267 options.sounds_directory,
3268 TREE_TYPE_SOUNDS_DIR);
3269 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3271 TREE_TYPE_SOUNDS_DIR);
3273 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3274 options.music_directory,
3275 TREE_TYPE_MUSIC_DIR);
3276 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3278 TREE_TYPE_MUSIC_DIR);
3280 if (artwork.gfx_first == NULL)
3281 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3282 if (artwork.snd_first == NULL)
3283 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3284 if (artwork.mus_first == NULL)
3285 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3287 /* before sorting, the first entries will be from the user directory */
3288 artwork.gfx_current =
3289 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3290 if (artwork.gfx_current == NULL)
3291 artwork.gfx_current =
3292 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3293 if (artwork.gfx_current == NULL)
3294 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3296 artwork.snd_current =
3297 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3298 if (artwork.snd_current == NULL)
3299 artwork.snd_current =
3300 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3301 if (artwork.snd_current == NULL)
3302 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3304 artwork.mus_current =
3305 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3306 if (artwork.mus_current == NULL)
3307 artwork.mus_current =
3308 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3309 if (artwork.mus_current == NULL)
3310 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3312 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3313 artwork.snd_current_identifier = artwork.snd_current->identifier;
3314 artwork.mus_current_identifier = artwork.mus_current->identifier;
3316 #if ENABLE_UNUSED_CODE
3317 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3318 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3319 printf("music set == %s\n\n", artwork.mus_current_identifier);
3322 sortTreeInfo(&artwork.gfx_first);
3323 sortTreeInfo(&artwork.snd_first);
3324 sortTreeInfo(&artwork.mus_first);
3326 #if ENABLE_UNUSED_CODE
3327 dumpTreeInfo(artwork.gfx_first, 0);
3328 dumpTreeInfo(artwork.snd_first, 0);
3329 dumpTreeInfo(artwork.mus_first, 0);
3333 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3334 LevelDirTree *level_node)
3336 int type = (*artwork_node)->type;
3338 /* recursively check all level directories for artwork sub-directories */
3342 /* check all tree entries for artwork, but skip parent link entries */
3343 if (!level_node->parent_link)
3345 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3346 boolean cached = (artwork_new != NULL);
3350 pushTreeInfo(artwork_node, artwork_new);
3354 TreeInfo *topnode_last = *artwork_node;
3355 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3356 ARTWORK_DIRECTORY(type));
3358 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3360 if (topnode_last != *artwork_node) /* check for newly added node */
3362 artwork_new = *artwork_node;
3364 setString(&artwork_new->identifier, level_node->subdir);
3365 setString(&artwork_new->name, level_node->name);
3366 setString(&artwork_new->name_sorting, level_node->name_sorting);
3368 artwork_new->sort_priority = level_node->sort_priority;
3369 artwork_new->color = LEVELCOLOR(artwork_new);
3375 /* insert artwork info (from old cache or filesystem) into new cache */
3376 if (artwork_new != NULL)
3377 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3380 DrawInitText(level_node->name, 150, FC_YELLOW);
3382 if (level_node->node_group != NULL)
3383 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3385 level_node = level_node->next;
3389 void LoadLevelArtworkInfo()
3391 print_timestamp_init("LoadLevelArtworkInfo");
3393 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3395 print_timestamp_time("DrawTimeText");
3397 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3398 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3399 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3400 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3401 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3402 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3404 SaveArtworkInfoCache();
3406 print_timestamp_time("SaveArtworkInfoCache");
3408 /* needed for reloading level artwork not known at ealier stage */
3410 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3412 artwork.gfx_current =
3413 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3414 if (artwork.gfx_current == NULL)
3415 artwork.gfx_current =
3416 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3417 if (artwork.gfx_current == NULL)
3418 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3421 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3423 artwork.snd_current =
3424 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3425 if (artwork.snd_current == NULL)
3426 artwork.snd_current =
3427 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3428 if (artwork.snd_current == NULL)
3429 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3432 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3434 artwork.mus_current =
3435 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3436 if (artwork.mus_current == NULL)
3437 artwork.mus_current =
3438 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3439 if (artwork.mus_current == NULL)
3440 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3443 print_timestamp_time("getTreeInfoFromIdentifier");
3445 sortTreeInfo(&artwork.gfx_first);
3446 sortTreeInfo(&artwork.snd_first);
3447 sortTreeInfo(&artwork.mus_first);
3449 print_timestamp_time("sortTreeInfo");
3451 #if ENABLE_UNUSED_CODE
3452 dumpTreeInfo(artwork.gfx_first, 0);
3453 dumpTreeInfo(artwork.snd_first, 0);
3454 dumpTreeInfo(artwork.mus_first, 0);
3457 print_timestamp_done("LoadLevelArtworkInfo");
3460 static void SaveUserLevelInfo()
3462 LevelDirTree *level_info;
3467 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3469 if (!(file = fopen(filename, MODE_WRITE)))
3471 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3476 level_info = newTreeInfo();
3478 /* always start with reliable default values */
3479 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3481 setString(&level_info->name, getLoginName());
3482 setString(&level_info->author, getRealName());
3483 level_info->levels = 100;
3484 level_info->first_level = 1;
3486 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3488 fprintFileHeader(file, LEVELINFO_FILENAME);
3491 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3493 if (i == LEVELINFO_TOKEN_NAME ||
3494 i == LEVELINFO_TOKEN_AUTHOR ||
3495 i == LEVELINFO_TOKEN_LEVELS ||
3496 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3497 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3499 /* just to make things nicer :) */
3500 if (i == LEVELINFO_TOKEN_AUTHOR)
3501 fprintf(file, "\n");
3504 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3508 SetFilePermissions(filename, PERMS_PRIVATE);
3510 freeTreeInfo(level_info);
3514 char *getSetupValue(int type, void *value)
3516 static char value_string[MAX_LINE_LEN];
3524 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3528 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3532 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3533 *(int *)value == FALSE ? "off" : "on"));
3537 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3540 case TYPE_YES_NO_AUTO:
3541 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3542 *(int *)value == FALSE ? "no" : "yes"));
3546 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3550 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3554 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3558 sprintf(value_string, "%d", *(int *)value);
3562 if (*(char **)value == NULL)
3565 strcpy(value_string, *(char **)value);
3569 value_string[0] = '\0';
3573 if (type & TYPE_GHOSTED)
3574 strcpy(value_string, "n/a");
3576 return value_string;
3579 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3583 static char token_string[MAX_LINE_LEN];
3584 int token_type = token_info[token_nr].type;
3585 void *setup_value = token_info[token_nr].value;
3586 char *token_text = token_info[token_nr].text;
3587 char *value_string = getSetupValue(token_type, setup_value);
3589 /* build complete token string */
3590 sprintf(token_string, "%s%s", prefix, token_text);
3592 /* build setup entry line */
3593 line = getFormattedSetupEntry(token_string, value_string);
3595 if (token_type == TYPE_KEY_X11)
3597 Key key = *(Key *)setup_value;
3598 char *keyname = getKeyNameFromKey(key);
3600 /* add comment, if useful */
3601 if (!strEqual(keyname, "(undefined)") &&
3602 !strEqual(keyname, "(unknown)"))
3604 /* add at least one whitespace */
3606 for (i = strlen(line); i < token_comment_position; i++)
3610 strcat(line, keyname);
3617 void LoadLevelSetup_LastSeries()
3619 /* ----------------------------------------------------------------------- */
3620 /* ~/.<program>/levelsetup.conf */
3621 /* ----------------------------------------------------------------------- */
3623 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3624 SetupFileHash *level_setup_hash = NULL;
3626 /* always start with reliable default values */
3627 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3629 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3630 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3632 if (leveldir_current == NULL)
3633 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3636 if ((level_setup_hash = loadSetupFileHash(filename)))
3638 char *last_level_series =
3639 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3641 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3643 if (leveldir_current == NULL)
3644 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3646 freeSetupFileHash(level_setup_hash);
3649 Error(ERR_WARN, "using default setup values");
3654 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3656 /* ----------------------------------------------------------------------- */
3657 /* ~/.<program>/levelsetup.conf */
3658 /* ----------------------------------------------------------------------- */
3660 // check if the current level directory structure is available at this point
3661 if (leveldir_current == NULL)
3664 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3665 char *level_subdir = leveldir_current->subdir;
3668 InitUserDataDirectory();
3670 if (!(file = fopen(filename, MODE_WRITE)))
3672 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3679 fprintFileHeader(file, LEVELSETUP_FILENAME);
3681 if (deactivate_last_level_series)
3682 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3684 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3689 SetFilePermissions(filename, PERMS_PRIVATE);
3694 void SaveLevelSetup_LastSeries()
3696 SaveLevelSetup_LastSeries_Ext(FALSE);
3699 void SaveLevelSetup_LastSeries_Deactivate()
3701 SaveLevelSetup_LastSeries_Ext(TRUE);
3704 static void checkSeriesInfo()
3706 static char *level_directory = NULL;
3709 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3711 level_directory = getPath2((leveldir_current->in_user_dir ?
3712 getUserLevelDir(NULL) :
3713 options.level_directory),
3714 leveldir_current->fullpath);
3716 if ((dir = openDirectory(level_directory)) == NULL)
3718 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3723 closeDirectory(dir);
3726 void LoadLevelSetup_SeriesInfo()
3729 SetupFileHash *level_setup_hash = NULL;
3730 char *level_subdir = leveldir_current->subdir;
3733 /* always start with reliable default values */
3734 level_nr = leveldir_current->first_level;
3736 for (i = 0; i < MAX_LEVELS; i++)
3738 LevelStats_setPlayed(i, 0);
3739 LevelStats_setSolved(i, 0);
3742 checkSeriesInfo(leveldir_current);
3744 /* ----------------------------------------------------------------------- */
3745 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3746 /* ----------------------------------------------------------------------- */
3748 level_subdir = leveldir_current->subdir;
3750 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3752 if ((level_setup_hash = loadSetupFileHash(filename)))
3756 /* get last played level in this level set */
3758 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3762 level_nr = atoi(token_value);
3764 if (level_nr < leveldir_current->first_level)
3765 level_nr = leveldir_current->first_level;
3766 if (level_nr > leveldir_current->last_level)
3767 level_nr = leveldir_current->last_level;
3770 /* get handicap level in this level set */
3772 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3776 int level_nr = atoi(token_value);
3778 if (level_nr < leveldir_current->first_level)
3779 level_nr = leveldir_current->first_level;
3780 if (level_nr > leveldir_current->last_level + 1)
3781 level_nr = leveldir_current->last_level;
3783 if (leveldir_current->user_defined || !leveldir_current->handicap)
3784 level_nr = leveldir_current->last_level;
3786 leveldir_current->handicap_level = level_nr;
3789 /* get number of played and solved levels in this level set */
3791 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3793 char *token = HASH_ITERATION_TOKEN(itr);
3794 char *value = HASH_ITERATION_VALUE(itr);
3796 if (strlen(token) == 3 &&
3797 token[0] >= '0' && token[0] <= '9' &&
3798 token[1] >= '0' && token[1] <= '9' &&
3799 token[2] >= '0' && token[2] <= '9')
3801 int level_nr = atoi(token);
3804 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3806 value = strchr(value, ' ');
3809 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3812 END_HASH_ITERATION(hash, itr)
3814 freeSetupFileHash(level_setup_hash);
3817 Error(ERR_WARN, "using default setup values");
3822 void SaveLevelSetup_SeriesInfo()
3825 char *level_subdir = leveldir_current->subdir;
3826 char *level_nr_str = int2str(level_nr, 0);
3827 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3831 /* ----------------------------------------------------------------------- */
3832 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3833 /* ----------------------------------------------------------------------- */
3835 InitLevelSetupDirectory(level_subdir);
3837 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3839 if (!(file = fopen(filename, MODE_WRITE)))
3841 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3846 fprintFileHeader(file, LEVELSETUP_FILENAME);
3848 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3850 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3851 handicap_level_str));
3853 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3856 if (LevelStats_getPlayed(i) > 0 ||
3857 LevelStats_getSolved(i) > 0)
3862 sprintf(token, "%03d", i);
3863 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3865 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3871 SetFilePermissions(filename, PERMS_PRIVATE);
3876 int LevelStats_getPlayed(int nr)
3878 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3881 int LevelStats_getSolved(int nr)
3883 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3886 void LevelStats_setPlayed(int nr, int value)
3888 if (nr >= 0 && nr < MAX_LEVELS)
3889 level_stats[nr].played = value;
3892 void LevelStats_setSolved(int nr, int value)
3894 if (nr >= 0 && nr < MAX_LEVELS)
3895 level_stats[nr].solved = value;
3898 void LevelStats_incPlayed(int nr)
3900 if (nr >= 0 && nr < MAX_LEVELS)
3901 level_stats[nr].played++;
3904 void LevelStats_incSolved(int nr)
3906 if (nr >= 0 && nr < MAX_LEVELS)
3907 level_stats[nr].solved++;