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 void 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, "..");
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);
2574 /* -------------------------------------------------------------------------- */
2575 /* functions for handling level and custom artwork info cache */
2576 /* -------------------------------------------------------------------------- */
2578 static void LoadArtworkInfoCache()
2580 InitCacheDirectory();
2582 if (artworkinfo_cache_old == NULL)
2584 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2586 /* try to load artwork info hash from already existing cache file */
2587 artworkinfo_cache_old = loadSetupFileHash(filename);
2589 /* if no artwork info cache file was found, start with empty hash */
2590 if (artworkinfo_cache_old == NULL)
2591 artworkinfo_cache_old = newSetupFileHash();
2596 if (artworkinfo_cache_new == NULL)
2597 artworkinfo_cache_new = newSetupFileHash();
2600 static void SaveArtworkInfoCache()
2602 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2604 InitCacheDirectory();
2606 saveSetupFileHash(artworkinfo_cache_new, filename);
2611 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2613 static char *prefix = NULL;
2615 checked_free(prefix);
2617 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2622 /* (identical to above function, but separate string buffer needed -- nasty) */
2623 static char *getCacheToken(char *prefix, char *suffix)
2625 static char *token = NULL;
2627 checked_free(token);
2629 token = getStringCat2WithSeparator(prefix, suffix, ".");
2634 static char *getFileTimestampString(char *filename)
2636 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2639 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2641 struct stat file_status;
2643 if (timestamp_string == NULL)
2646 if (stat(filename, &file_status) != 0) /* cannot stat file */
2649 return (file_status.st_mtime != atoi(timestamp_string));
2652 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2654 char *identifier = level_node->subdir;
2655 char *type_string = ARTWORK_DIRECTORY(type);
2656 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2657 char *token_main = getCacheToken(token_prefix, "CACHED");
2658 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2659 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2660 TreeInfo *artwork_info = NULL;
2662 if (!use_artworkinfo_cache)
2669 artwork_info = newTreeInfo();
2670 setTreeInfoToDefaults(artwork_info, type);
2672 /* set all structure fields according to the token/value pairs */
2673 ldi = *artwork_info;
2674 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2676 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2677 char *value = getHashEntry(artworkinfo_cache_old, token);
2679 setSetupInfo(artworkinfo_tokens, i, value);
2681 /* check if cache entry for this item is invalid or incomplete */
2684 Error(ERR_WARN, "cache entry '%s' invalid", token);
2690 *artwork_info = ldi;
2695 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2696 LEVELINFO_FILENAME);
2697 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2698 ARTWORKINFO_FILENAME(type));
2700 /* check if corresponding "levelinfo.conf" file has changed */
2701 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2702 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2704 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2707 /* check if corresponding "<artworkinfo>.conf" file has changed */
2708 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2709 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2711 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2714 checked_free(filename_levelinfo);
2715 checked_free(filename_artworkinfo);
2718 if (!cached && artwork_info != NULL)
2720 freeTreeInfo(artwork_info);
2725 return artwork_info;
2728 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2729 LevelDirTree *level_node, int type)
2731 char *identifier = level_node->subdir;
2732 char *type_string = ARTWORK_DIRECTORY(type);
2733 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2734 char *token_main = getCacheToken(token_prefix, "CACHED");
2735 boolean set_cache_timestamps = TRUE;
2738 setHashEntry(artworkinfo_cache_new, token_main, "true");
2740 if (set_cache_timestamps)
2742 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2743 LEVELINFO_FILENAME);
2744 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2745 ARTWORKINFO_FILENAME(type));
2746 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2747 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2749 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2750 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2752 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2753 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2755 checked_free(filename_levelinfo);
2756 checked_free(filename_artworkinfo);
2757 checked_free(timestamp_levelinfo);
2758 checked_free(timestamp_artworkinfo);
2761 ldi = *artwork_info;
2762 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2764 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2765 char *value = getSetupValue(artworkinfo_tokens[i].type,
2766 artworkinfo_tokens[i].value);
2768 setHashEntry(artworkinfo_cache_new, token, value);
2773 /* -------------------------------------------------------------------------- */
2774 /* functions for loading level info and custom artwork info */
2775 /* -------------------------------------------------------------------------- */
2777 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2778 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2780 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2781 TreeInfo *node_parent,
2782 char *level_directory,
2783 char *directory_name)
2785 char *directory_path = getPath2(level_directory, directory_name);
2786 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2787 SetupFileHash *setup_file_hash;
2788 LevelDirTree *leveldir_new = NULL;
2791 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2792 if (!options.debug && !fileExists(filename))
2794 free(directory_path);
2800 setup_file_hash = loadSetupFileHash(filename);
2802 if (setup_file_hash == NULL)
2804 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2806 free(directory_path);
2812 leveldir_new = newTreeInfo();
2815 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2817 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2819 leveldir_new->subdir = getStringCopy(directory_name);
2821 /* set all structure fields according to the token/value pairs */
2822 ldi = *leveldir_new;
2823 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2824 setSetupInfo(levelinfo_tokens, i,
2825 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2826 *leveldir_new = ldi;
2828 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2829 setString(&leveldir_new->name, leveldir_new->subdir);
2831 if (leveldir_new->identifier == NULL)
2832 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2834 if (leveldir_new->name_sorting == NULL)
2835 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2837 if (node_parent == NULL) /* top level group */
2839 leveldir_new->basepath = getStringCopy(level_directory);
2840 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2842 else /* sub level group */
2844 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2845 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2848 leveldir_new->last_level =
2849 leveldir_new->first_level + leveldir_new->levels - 1;
2851 leveldir_new->in_user_dir =
2852 (!strEqual(leveldir_new->basepath, options.level_directory));
2854 /* adjust some settings if user's private level directory was detected */
2855 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2856 leveldir_new->in_user_dir &&
2857 (strEqual(leveldir_new->subdir, getLoginName()) ||
2858 strEqual(leveldir_new->name, getLoginName()) ||
2859 strEqual(leveldir_new->author, getRealName())))
2861 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2862 leveldir_new->readonly = FALSE;
2865 leveldir_new->user_defined =
2866 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2868 leveldir_new->color = LEVELCOLOR(leveldir_new);
2870 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2872 leveldir_new->handicap_level = /* set handicap to default value */
2873 (leveldir_new->user_defined || !leveldir_new->handicap ?
2874 leveldir_new->last_level : leveldir_new->first_level);
2876 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2878 pushTreeInfo(node_first, leveldir_new);
2880 freeSetupFileHash(setup_file_hash);
2882 if (leveldir_new->level_group)
2884 /* create node to link back to current level directory */
2885 createParentTreeInfoNode(leveldir_new);
2887 /* recursively step into sub-directory and look for more level series */
2888 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2889 leveldir_new, directory_path);
2892 free(directory_path);
2898 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2899 TreeInfo *node_parent,
2900 char *level_directory)
2903 DirectoryEntry *dir_entry;
2904 boolean valid_entry_found = FALSE;
2906 if ((dir = openDirectory(level_directory)) == NULL)
2908 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2913 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2915 char *directory_name = dir_entry->basename;
2916 char *directory_path = getPath2(level_directory, directory_name);
2918 /* skip entries for current and parent directory */
2919 if (strEqual(directory_name, ".") ||
2920 strEqual(directory_name, ".."))
2922 free(directory_path);
2927 /* find out if directory entry is itself a directory */
2928 if (!dir_entry->is_directory) /* not a directory */
2930 free(directory_path);
2935 free(directory_path);
2937 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2938 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2939 strEqual(directory_name, MUSIC_DIRECTORY))
2942 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2947 closeDirectory(dir);
2949 /* special case: top level directory may directly contain "levelinfo.conf" */
2950 if (node_parent == NULL && !valid_entry_found)
2952 /* check if this directory directly contains a file "levelinfo.conf" */
2953 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2954 level_directory, ".");
2957 if (!valid_entry_found)
2958 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2962 boolean AdjustGraphicsForEMC()
2964 boolean settings_changed = FALSE;
2966 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2967 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2969 return settings_changed;
2972 void LoadLevelInfo()
2974 InitUserLevelDirectory(getLoginName());
2976 DrawInitText("Loading level series", 120, FC_GREEN);
2978 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2979 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2981 /* after loading all level set information, clone the level directory tree
2982 and remove all level sets without levels (these may still contain artwork
2983 to be offered in the setup menu as "custom artwork", and are therefore
2984 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2985 leveldir_first_all = leveldir_first;
2986 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2988 AdjustGraphicsForEMC();
2990 /* before sorting, the first entries will be from the user directory */
2991 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2993 if (leveldir_first == NULL)
2994 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2996 sortTreeInfo(&leveldir_first);
2998 #if ENABLE_UNUSED_CODE
2999 dumpTreeInfo(leveldir_first, 0);
3003 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3004 TreeInfo *node_parent,
3005 char *base_directory,
3006 char *directory_name, int type)
3008 char *directory_path = getPath2(base_directory, directory_name);
3009 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3010 SetupFileHash *setup_file_hash = NULL;
3011 TreeInfo *artwork_new = NULL;
3014 if (fileExists(filename))
3015 setup_file_hash = loadSetupFileHash(filename);
3017 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3020 DirectoryEntry *dir_entry;
3021 boolean valid_file_found = FALSE;
3023 if ((dir = openDirectory(directory_path)) != NULL)
3025 while ((dir_entry = readDirectory(dir)) != NULL)
3027 if (FileIsArtworkType(dir_entry->filename, type))
3029 valid_file_found = TRUE;
3035 closeDirectory(dir);
3038 if (!valid_file_found)
3040 if (!strEqual(directory_name, "."))
3041 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3043 free(directory_path);
3050 artwork_new = newTreeInfo();
3053 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3055 setTreeInfoToDefaults(artwork_new, type);
3057 artwork_new->subdir = getStringCopy(directory_name);
3059 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3061 /* set all structure fields according to the token/value pairs */
3063 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3064 setSetupInfo(levelinfo_tokens, i,
3065 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3068 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3069 setString(&artwork_new->name, artwork_new->subdir);
3071 if (artwork_new->identifier == NULL)
3072 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3074 if (artwork_new->name_sorting == NULL)
3075 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3078 if (node_parent == NULL) /* top level group */
3080 artwork_new->basepath = getStringCopy(base_directory);
3081 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3083 else /* sub level group */
3085 artwork_new->basepath = getStringCopy(node_parent->basepath);
3086 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3089 artwork_new->in_user_dir =
3090 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3092 /* (may use ".sort_priority" from "setup_file_hash" above) */
3093 artwork_new->color = ARTWORKCOLOR(artwork_new);
3095 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3097 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3099 if (strEqual(artwork_new->subdir, "."))
3101 if (artwork_new->user_defined)
3103 setString(&artwork_new->identifier, "private");
3104 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3108 setString(&artwork_new->identifier, "classic");
3109 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3112 /* set to new values after changing ".sort_priority" */
3113 artwork_new->color = ARTWORKCOLOR(artwork_new);
3115 setString(&artwork_new->class_desc,
3116 getLevelClassDescription(artwork_new));
3120 setString(&artwork_new->identifier, artwork_new->subdir);
3123 setString(&artwork_new->name, artwork_new->identifier);
3124 setString(&artwork_new->name_sorting, artwork_new->name);
3127 pushTreeInfo(node_first, artwork_new);
3129 freeSetupFileHash(setup_file_hash);
3131 free(directory_path);
3137 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3138 TreeInfo *node_parent,
3139 char *base_directory, int type)
3142 DirectoryEntry *dir_entry;
3143 boolean valid_entry_found = FALSE;
3145 if ((dir = openDirectory(base_directory)) == NULL)
3147 /* display error if directory is main "options.graphics_directory" etc. */
3148 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3149 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3154 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3156 char *directory_name = dir_entry->basename;
3157 char *directory_path = getPath2(base_directory, directory_name);
3159 /* skip directory entries for current and parent directory */
3160 if (strEqual(directory_name, ".") ||
3161 strEqual(directory_name, ".."))
3163 free(directory_path);
3168 /* skip directory entries which are not a directory */
3169 if (!dir_entry->is_directory) /* not a directory */
3171 free(directory_path);
3176 free(directory_path);
3178 /* check if this directory contains artwork with or without config file */
3179 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3181 directory_name, type);
3184 closeDirectory(dir);
3186 /* check if this directory directly contains artwork itself */
3187 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3188 base_directory, ".",
3190 if (!valid_entry_found)
3191 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3195 static TreeInfo *getDummyArtworkInfo(int type)
3197 /* this is only needed when there is completely no artwork available */
3198 TreeInfo *artwork_new = newTreeInfo();
3200 setTreeInfoToDefaults(artwork_new, type);
3202 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3203 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3204 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3206 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3207 setString(&artwork_new->name, UNDEFINED_FILENAME);
3208 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3213 void LoadArtworkInfo()
3215 LoadArtworkInfoCache();
3217 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3219 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3220 options.graphics_directory,
3221 TREE_TYPE_GRAPHICS_DIR);
3222 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3223 getUserGraphicsDir(),
3224 TREE_TYPE_GRAPHICS_DIR);
3226 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3227 options.sounds_directory,
3228 TREE_TYPE_SOUNDS_DIR);
3229 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3231 TREE_TYPE_SOUNDS_DIR);
3233 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3234 options.music_directory,
3235 TREE_TYPE_MUSIC_DIR);
3236 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3238 TREE_TYPE_MUSIC_DIR);
3240 if (artwork.gfx_first == NULL)
3241 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3242 if (artwork.snd_first == NULL)
3243 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3244 if (artwork.mus_first == NULL)
3245 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3247 /* before sorting, the first entries will be from the user directory */
3248 artwork.gfx_current =
3249 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3250 if (artwork.gfx_current == NULL)
3251 artwork.gfx_current =
3252 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3253 if (artwork.gfx_current == NULL)
3254 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3256 artwork.snd_current =
3257 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3258 if (artwork.snd_current == NULL)
3259 artwork.snd_current =
3260 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3261 if (artwork.snd_current == NULL)
3262 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3264 artwork.mus_current =
3265 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3266 if (artwork.mus_current == NULL)
3267 artwork.mus_current =
3268 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3269 if (artwork.mus_current == NULL)
3270 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3272 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3273 artwork.snd_current_identifier = artwork.snd_current->identifier;
3274 artwork.mus_current_identifier = artwork.mus_current->identifier;
3276 #if ENABLE_UNUSED_CODE
3277 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3278 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3279 printf("music set == %s\n\n", artwork.mus_current_identifier);
3282 sortTreeInfo(&artwork.gfx_first);
3283 sortTreeInfo(&artwork.snd_first);
3284 sortTreeInfo(&artwork.mus_first);
3286 #if ENABLE_UNUSED_CODE
3287 dumpTreeInfo(artwork.gfx_first, 0);
3288 dumpTreeInfo(artwork.snd_first, 0);
3289 dumpTreeInfo(artwork.mus_first, 0);
3293 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3294 LevelDirTree *level_node)
3296 int type = (*artwork_node)->type;
3298 /* recursively check all level directories for artwork sub-directories */
3302 /* check all tree entries for artwork, but skip parent link entries */
3303 if (!level_node->parent_link)
3305 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3306 boolean cached = (artwork_new != NULL);
3310 pushTreeInfo(artwork_node, artwork_new);
3314 TreeInfo *topnode_last = *artwork_node;
3315 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3316 ARTWORK_DIRECTORY(type));
3318 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3320 if (topnode_last != *artwork_node) /* check for newly added node */
3322 artwork_new = *artwork_node;
3324 setString(&artwork_new->identifier, level_node->subdir);
3325 setString(&artwork_new->name, level_node->name);
3326 setString(&artwork_new->name_sorting, level_node->name_sorting);
3328 artwork_new->sort_priority = level_node->sort_priority;
3329 artwork_new->color = LEVELCOLOR(artwork_new);
3335 /* insert artwork info (from old cache or filesystem) into new cache */
3336 if (artwork_new != NULL)
3337 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3340 DrawInitText(level_node->name, 150, FC_YELLOW);
3342 if (level_node->node_group != NULL)
3343 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3345 level_node = level_node->next;
3349 void LoadLevelArtworkInfo()
3351 print_timestamp_init("LoadLevelArtworkInfo");
3353 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3355 print_timestamp_time("DrawTimeText");
3357 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3358 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3359 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3360 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3361 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3362 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3364 SaveArtworkInfoCache();
3366 print_timestamp_time("SaveArtworkInfoCache");
3368 /* needed for reloading level artwork not known at ealier stage */
3370 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3372 artwork.gfx_current =
3373 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3374 if (artwork.gfx_current == NULL)
3375 artwork.gfx_current =
3376 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3377 if (artwork.gfx_current == NULL)
3378 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3381 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3383 artwork.snd_current =
3384 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3385 if (artwork.snd_current == NULL)
3386 artwork.snd_current =
3387 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3388 if (artwork.snd_current == NULL)
3389 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3392 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3394 artwork.mus_current =
3395 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3396 if (artwork.mus_current == NULL)
3397 artwork.mus_current =
3398 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3399 if (artwork.mus_current == NULL)
3400 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3403 print_timestamp_time("getTreeInfoFromIdentifier");
3405 sortTreeInfo(&artwork.gfx_first);
3406 sortTreeInfo(&artwork.snd_first);
3407 sortTreeInfo(&artwork.mus_first);
3409 print_timestamp_time("sortTreeInfo");
3411 #if ENABLE_UNUSED_CODE
3412 dumpTreeInfo(artwork.gfx_first, 0);
3413 dumpTreeInfo(artwork.snd_first, 0);
3414 dumpTreeInfo(artwork.mus_first, 0);
3417 print_timestamp_done("LoadLevelArtworkInfo");
3420 static void SaveUserLevelInfo()
3422 LevelDirTree *level_info;
3427 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3429 if (!(file = fopen(filename, MODE_WRITE)))
3431 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3436 level_info = newTreeInfo();
3438 /* always start with reliable default values */
3439 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3441 setString(&level_info->name, getLoginName());
3442 setString(&level_info->author, getRealName());
3443 level_info->levels = 100;
3444 level_info->first_level = 1;
3446 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3448 fprintFileHeader(file, LEVELINFO_FILENAME);
3451 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3453 if (i == LEVELINFO_TOKEN_NAME ||
3454 i == LEVELINFO_TOKEN_AUTHOR ||
3455 i == LEVELINFO_TOKEN_LEVELS ||
3456 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3457 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3459 /* just to make things nicer :) */
3460 if (i == LEVELINFO_TOKEN_AUTHOR)
3461 fprintf(file, "\n");
3464 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3468 SetFilePermissions(filename, PERMS_PRIVATE);
3470 freeTreeInfo(level_info);
3474 char *getSetupValue(int type, void *value)
3476 static char value_string[MAX_LINE_LEN];
3484 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3488 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3492 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3493 *(int *)value == FALSE ? "off" : "on"));
3497 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3500 case TYPE_YES_NO_AUTO:
3501 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3502 *(int *)value == FALSE ? "no" : "yes"));
3506 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3510 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3514 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3518 sprintf(value_string, "%d", *(int *)value);
3522 if (*(char **)value == NULL)
3525 strcpy(value_string, *(char **)value);
3529 value_string[0] = '\0';
3533 if (type & TYPE_GHOSTED)
3534 strcpy(value_string, "n/a");
3536 return value_string;
3539 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3543 static char token_string[MAX_LINE_LEN];
3544 int token_type = token_info[token_nr].type;
3545 void *setup_value = token_info[token_nr].value;
3546 char *token_text = token_info[token_nr].text;
3547 char *value_string = getSetupValue(token_type, setup_value);
3549 /* build complete token string */
3550 sprintf(token_string, "%s%s", prefix, token_text);
3552 /* build setup entry line */
3553 line = getFormattedSetupEntry(token_string, value_string);
3555 if (token_type == TYPE_KEY_X11)
3557 Key key = *(Key *)setup_value;
3558 char *keyname = getKeyNameFromKey(key);
3560 /* add comment, if useful */
3561 if (!strEqual(keyname, "(undefined)") &&
3562 !strEqual(keyname, "(unknown)"))
3564 /* add at least one whitespace */
3566 for (i = strlen(line); i < token_comment_position; i++)
3570 strcat(line, keyname);
3577 void LoadLevelSetup_LastSeries()
3579 /* ----------------------------------------------------------------------- */
3580 /* ~/.<program>/levelsetup.conf */
3581 /* ----------------------------------------------------------------------- */
3583 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3584 SetupFileHash *level_setup_hash = NULL;
3586 /* always start with reliable default values */
3587 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3589 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3590 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3592 if (leveldir_current == NULL)
3593 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3596 if ((level_setup_hash = loadSetupFileHash(filename)))
3598 char *last_level_series =
3599 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3601 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3603 if (leveldir_current == NULL)
3604 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3606 freeSetupFileHash(level_setup_hash);
3609 Error(ERR_WARN, "using default setup values");
3614 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3616 /* ----------------------------------------------------------------------- */
3617 /* ~/.<program>/levelsetup.conf */
3618 /* ----------------------------------------------------------------------- */
3620 // check if the current level directory structure is available at this point
3621 if (leveldir_current == NULL)
3624 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3625 char *level_subdir = leveldir_current->subdir;
3628 InitUserDataDirectory();
3630 if (!(file = fopen(filename, MODE_WRITE)))
3632 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3639 fprintFileHeader(file, LEVELSETUP_FILENAME);
3641 if (deactivate_last_level_series)
3642 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3644 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3649 SetFilePermissions(filename, PERMS_PRIVATE);
3654 void SaveLevelSetup_LastSeries()
3656 SaveLevelSetup_LastSeries_Ext(FALSE);
3659 void SaveLevelSetup_LastSeries_Deactivate()
3661 SaveLevelSetup_LastSeries_Ext(TRUE);
3664 static void checkSeriesInfo()
3666 static char *level_directory = NULL;
3669 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3671 level_directory = getPath2((leveldir_current->in_user_dir ?
3672 getUserLevelDir(NULL) :
3673 options.level_directory),
3674 leveldir_current->fullpath);
3676 if ((dir = openDirectory(level_directory)) == NULL)
3678 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3683 closeDirectory(dir);
3686 void LoadLevelSetup_SeriesInfo()
3689 SetupFileHash *level_setup_hash = NULL;
3690 char *level_subdir = leveldir_current->subdir;
3693 /* always start with reliable default values */
3694 level_nr = leveldir_current->first_level;
3696 for (i = 0; i < MAX_LEVELS; i++)
3698 LevelStats_setPlayed(i, 0);
3699 LevelStats_setSolved(i, 0);
3702 checkSeriesInfo(leveldir_current);
3704 /* ----------------------------------------------------------------------- */
3705 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3706 /* ----------------------------------------------------------------------- */
3708 level_subdir = leveldir_current->subdir;
3710 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3712 if ((level_setup_hash = loadSetupFileHash(filename)))
3716 /* get last played level in this level set */
3718 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3722 level_nr = atoi(token_value);
3724 if (level_nr < leveldir_current->first_level)
3725 level_nr = leveldir_current->first_level;
3726 if (level_nr > leveldir_current->last_level)
3727 level_nr = leveldir_current->last_level;
3730 /* get handicap level in this level set */
3732 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3736 int level_nr = atoi(token_value);
3738 if (level_nr < leveldir_current->first_level)
3739 level_nr = leveldir_current->first_level;
3740 if (level_nr > leveldir_current->last_level + 1)
3741 level_nr = leveldir_current->last_level;
3743 if (leveldir_current->user_defined || !leveldir_current->handicap)
3744 level_nr = leveldir_current->last_level;
3746 leveldir_current->handicap_level = level_nr;
3749 /* get number of played and solved levels in this level set */
3751 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3753 char *token = HASH_ITERATION_TOKEN(itr);
3754 char *value = HASH_ITERATION_VALUE(itr);
3756 if (strlen(token) == 3 &&
3757 token[0] >= '0' && token[0] <= '9' &&
3758 token[1] >= '0' && token[1] <= '9' &&
3759 token[2] >= '0' && token[2] <= '9')
3761 int level_nr = atoi(token);
3764 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3766 value = strchr(value, ' ');
3769 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3772 END_HASH_ITERATION(hash, itr)
3774 freeSetupFileHash(level_setup_hash);
3777 Error(ERR_WARN, "using default setup values");
3782 void SaveLevelSetup_SeriesInfo()
3785 char *level_subdir = leveldir_current->subdir;
3786 char *level_nr_str = int2str(level_nr, 0);
3787 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3791 /* ----------------------------------------------------------------------- */
3792 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3793 /* ----------------------------------------------------------------------- */
3795 InitLevelSetupDirectory(level_subdir);
3797 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3799 if (!(file = fopen(filename, MODE_WRITE)))
3801 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3806 fprintFileHeader(file, LEVELSETUP_FILENAME);
3808 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3810 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3811 handicap_level_str));
3813 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3816 if (LevelStats_getPlayed(i) > 0 ||
3817 LevelStats_getSolved(i) > 0)
3822 sprintf(token, "%03d", i);
3823 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3825 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3831 SetFilePermissions(filename, PERMS_PRIVATE);
3836 int LevelStats_getPlayed(int nr)
3838 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3841 int LevelStats_getSolved(int nr)
3843 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3846 void LevelStats_setPlayed(int nr, int value)
3848 if (nr >= 0 && nr < MAX_LEVELS)
3849 level_stats[nr].played = value;
3852 void LevelStats_setSolved(int nr, int value)
3854 if (nr >= 0 && nr < MAX_LEVELS)
3855 level_stats[nr].solved = value;
3858 void LevelStats_incPlayed(int nr)
3860 if (nr >= 0 && nr < MAX_LEVELS)
3861 level_stats[nr].played++;
3864 void LevelStats_incSolved(int nr)
3866 if (nr >= 0 && nr < MAX_LEVELS)
3867 level_stats[nr].solved++;