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 USE_FILE_IDENTIFIERS FALSE /* do not use identifiers anymore */
34 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
36 #define NUM_LEVELCLASS_DESC 8
38 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
51 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
52 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
53 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
58 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
59 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
62 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
63 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
64 IS_LEVELCLASS_BD(n) ? 2 : \
65 IS_LEVELCLASS_EM(n) ? 3 : \
66 IS_LEVELCLASS_SP(n) ? 4 : \
67 IS_LEVELCLASS_DX(n) ? 5 : \
68 IS_LEVELCLASS_SB(n) ? 6 : \
69 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
70 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
73 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
74 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
75 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
76 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
79 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
80 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
81 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
82 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
85 #define TOKEN_VALUE_POSITION_SHORT 32
86 #define TOKEN_VALUE_POSITION_DEFAULT 40
87 #define TOKEN_COMMENT_POSITION_DEFAULT 60
89 #define MAX_COOKIE_LEN 256
92 static void setTreeInfoToDefaults(TreeInfo *, int);
93 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
94 static int compareTreeInfoEntries(const void *, const void *);
96 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
97 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
99 static SetupFileHash *artworkinfo_cache_old = NULL;
100 static SetupFileHash *artworkinfo_cache_new = NULL;
101 static boolean use_artworkinfo_cache = TRUE;
104 /* ------------------------------------------------------------------------- */
106 /* ------------------------------------------------------------------------- */
108 static char *getLevelClassDescription(TreeInfo *ti)
110 int position = ti->sort_priority / 100;
112 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
113 return levelclass_desc[position];
115 return "Unknown Level Class";
118 static char *getUserLevelDir(char *level_subdir)
120 static char *userlevel_dir = NULL;
121 char *data_dir = getUserGameDataDir();
122 char *userlevel_subdir = LEVELS_DIRECTORY;
124 checked_free(userlevel_dir);
126 if (level_subdir != NULL)
127 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
129 userlevel_dir = getPath2(data_dir, userlevel_subdir);
131 return userlevel_dir;
134 static char *getScoreDir(char *level_subdir)
136 static char *score_dir = NULL;
137 char *data_dir = getCommonDataDir();
138 char *score_subdir = SCORES_DIRECTORY;
140 checked_free(score_dir);
142 if (level_subdir != NULL)
143 score_dir = getPath3(data_dir, score_subdir, level_subdir);
145 score_dir = getPath2(data_dir, score_subdir);
150 static char *getLevelSetupDir(char *level_subdir)
152 static char *levelsetup_dir = NULL;
153 char *data_dir = getUserGameDataDir();
154 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
156 checked_free(levelsetup_dir);
158 if (level_subdir != NULL)
159 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
161 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
163 return levelsetup_dir;
166 static char *getCacheDir()
168 static char *cache_dir = NULL;
170 if (cache_dir == NULL)
171 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
176 static char *getLevelDirFromTreeInfo(TreeInfo *node)
178 static char *level_dir = NULL;
181 return options.level_directory;
183 checked_free(level_dir);
185 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
186 options.level_directory), node->fullpath);
191 char *getCurrentLevelDir()
193 return getLevelDirFromTreeInfo(leveldir_current);
196 static char *getTapeDir(char *level_subdir)
198 static char *tape_dir = NULL;
199 char *data_dir = getUserGameDataDir();
200 char *tape_subdir = TAPES_DIRECTORY;
202 checked_free(tape_dir);
204 if (level_subdir != NULL)
205 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
207 tape_dir = getPath2(data_dir, tape_subdir);
212 static char *getSolutionTapeDir()
214 static char *tape_dir = NULL;
215 char *data_dir = getCurrentLevelDir();
216 char *tape_subdir = TAPES_DIRECTORY;
218 checked_free(tape_dir);
220 tape_dir = getPath2(data_dir, tape_subdir);
225 static char *getDefaultGraphicsDir(char *graphics_subdir)
227 static char *graphics_dir = NULL;
229 if (graphics_subdir == NULL)
230 return options.graphics_directory;
232 checked_free(graphics_dir);
234 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
239 static char *getDefaultSoundsDir(char *sounds_subdir)
241 static char *sounds_dir = NULL;
243 if (sounds_subdir == NULL)
244 return options.sounds_directory;
246 checked_free(sounds_dir);
248 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
253 static char *getDefaultMusicDir(char *music_subdir)
255 static char *music_dir = NULL;
257 if (music_subdir == NULL)
258 return options.music_directory;
260 checked_free(music_dir);
262 music_dir = getPath2(options.music_directory, music_subdir);
267 static char *getClassicArtworkSet(int type)
269 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
270 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
271 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
274 static char *getClassicArtworkDir(int type)
276 return (type == TREE_TYPE_GRAPHICS_DIR ?
277 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_SOUNDS_DIR ?
279 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
280 type == TREE_TYPE_MUSIC_DIR ?
281 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
284 static char *getUserGraphicsDir()
286 static char *usergraphics_dir = NULL;
288 if (usergraphics_dir == NULL)
289 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
291 return usergraphics_dir;
294 static char *getUserSoundsDir()
296 static char *usersounds_dir = NULL;
298 if (usersounds_dir == NULL)
299 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
301 return usersounds_dir;
304 static char *getUserMusicDir()
306 static char *usermusic_dir = NULL;
308 if (usermusic_dir == NULL)
309 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
311 return usermusic_dir;
314 static char *getSetupArtworkDir(TreeInfo *ti)
316 static char *artwork_dir = NULL;
318 checked_free(artwork_dir);
320 artwork_dir = getPath2(ti->basepath, ti->fullpath);
325 char *setLevelArtworkDir(TreeInfo *ti)
327 char **artwork_path_ptr, **artwork_set_ptr;
328 TreeInfo *level_artwork;
330 if (ti == NULL || leveldir_current == NULL)
333 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
334 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
336 checked_free(*artwork_path_ptr);
338 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
340 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
345 No (or non-existing) artwork configured in "levelinfo.conf". This would
346 normally result in using the artwork configured in the setup menu. But
347 if an artwork subdirectory exists (which might contain custom artwork
348 or an artwork configuration file), this level artwork must be treated
349 as relative to the default "classic" artwork, not to the artwork that
350 is currently configured in the setup menu.
352 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
353 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
354 the real "classic" artwork from the original R'n'D (like "gfx_classic").
357 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
359 checked_free(*artwork_set_ptr);
361 if (directoryExists(dir))
363 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
364 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
368 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
369 *artwork_set_ptr = NULL;
375 return *artwork_set_ptr;
378 inline static char *getLevelArtworkSet(int type)
380 if (leveldir_current == NULL)
383 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
386 inline static char *getLevelArtworkDir(int type)
388 if (leveldir_current == NULL)
389 return UNDEFINED_FILENAME;
391 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
394 char *getTapeFilename(int nr)
396 static char *filename = NULL;
397 char basename[MAX_FILENAME_LEN];
399 checked_free(filename);
401 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
402 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
407 char *getSolutionTapeFilename(int nr)
409 static char *filename = NULL;
410 char basename[MAX_FILENAME_LEN];
412 checked_free(filename);
414 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
415 filename = getPath2(getSolutionTapeDir(), basename);
417 if (!fileExists(filename))
419 static char *filename_sln = NULL;
421 checked_free(filename_sln);
423 sprintf(basename, "%03d.sln", nr);
424 filename_sln = getPath2(getSolutionTapeDir(), basename);
426 if (fileExists(filename_sln))
433 char *getScoreFilename(int nr)
435 static char *filename = NULL;
436 char basename[MAX_FILENAME_LEN];
438 checked_free(filename);
440 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
441 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
446 char *getSetupFilename()
448 static char *filename = NULL;
450 checked_free(filename);
452 filename = getPath2(getSetupDir(), SETUP_FILENAME);
457 char *getEditorSetupFilename()
459 static char *filename = NULL;
461 checked_free(filename);
462 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
464 if (fileExists(filename))
467 checked_free(filename);
468 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
473 char *getHelpAnimFilename()
475 static char *filename = NULL;
477 checked_free(filename);
479 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
484 char *getHelpTextFilename()
486 static char *filename = NULL;
488 checked_free(filename);
490 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
495 char *getLevelSetInfoFilename()
497 static char *filename = NULL;
512 for (i = 0; basenames[i] != NULL; i++)
514 checked_free(filename);
515 filename = getPath2(getCurrentLevelDir(), basenames[i]);
517 if (fileExists(filename))
524 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
526 static char basename[32];
528 sprintf(basename, "%s_%d.txt",
529 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
534 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
536 static char *filename = NULL;
538 boolean skip_setup_artwork = FALSE;
540 checked_free(filename);
542 basename = getLevelSetTitleMessageBasename(nr, initial);
544 if (!gfx.override_level_graphics)
546 /* 1st try: look for special artwork in current level series directory */
547 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
548 if (fileExists(filename))
553 /* 2nd try: look for message file in current level set directory */
554 filename = getPath2(getCurrentLevelDir(), basename);
555 if (fileExists(filename))
560 /* check if there is special artwork configured in level series config */
561 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
563 /* 3rd try: look for special artwork configured in level series config */
564 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
565 if (fileExists(filename))
570 /* take missing artwork configured in level set config from default */
571 skip_setup_artwork = TRUE;
575 if (!skip_setup_artwork)
577 /* 4th try: look for special artwork in configured artwork directory */
578 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
579 if (fileExists(filename))
585 /* 5th try: look for default artwork in new default artwork directory */
586 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
587 if (fileExists(filename))
592 /* 6th try: look for default artwork in old default artwork directory */
593 filename = getPath2(options.graphics_directory, basename);
594 if (fileExists(filename))
597 return NULL; /* cannot find specified artwork file anywhere */
600 static char *getCorrectedArtworkBasename(char *basename)
605 char *getCustomImageFilename(char *basename)
607 static char *filename = NULL;
608 boolean skip_setup_artwork = FALSE;
610 checked_free(filename);
612 basename = getCorrectedArtworkBasename(basename);
614 if (!gfx.override_level_graphics)
616 /* 1st try: look for special artwork in current level series directory */
617 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
618 if (fileExists(filename))
623 /* check if there is special artwork configured in level series config */
624 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
626 /* 2nd try: look for special artwork configured in level series config */
627 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
628 if (fileExists(filename))
633 /* take missing artwork configured in level set config from default */
634 skip_setup_artwork = TRUE;
638 if (!skip_setup_artwork)
640 /* 3rd try: look for special artwork in configured artwork directory */
641 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
642 if (fileExists(filename))
648 /* 4th try: look for default artwork in new default artwork directory */
649 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
650 if (fileExists(filename))
655 /* 5th try: look for default artwork in old default artwork directory */
656 filename = getPath2(options.graphics_directory, basename);
657 if (fileExists(filename))
660 #if defined(CREATE_SPECIAL_EDITION)
664 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
666 /* 6th try: look for fallback artwork in old default artwork directory */
667 /* (needed to prevent errors when trying to access unused artwork files) */
668 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
669 if (fileExists(filename))
673 return NULL; /* cannot find specified artwork file anywhere */
676 char *getCustomSoundFilename(char *basename)
678 static char *filename = NULL;
679 boolean skip_setup_artwork = FALSE;
681 checked_free(filename);
683 basename = getCorrectedArtworkBasename(basename);
685 if (!gfx.override_level_sounds)
687 /* 1st try: look for special artwork in current level series directory */
688 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
689 if (fileExists(filename))
694 /* check if there is special artwork configured in level series config */
695 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
697 /* 2nd try: look for special artwork configured in level series config */
698 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
699 if (fileExists(filename))
704 /* take missing artwork configured in level set config from default */
705 skip_setup_artwork = TRUE;
709 if (!skip_setup_artwork)
711 /* 3rd try: look for special artwork in configured artwork directory */
712 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
713 if (fileExists(filename))
719 /* 4th try: look for default artwork in new default artwork directory */
720 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
721 if (fileExists(filename))
726 /* 5th try: look for default artwork in old default artwork directory */
727 filename = getPath2(options.sounds_directory, basename);
728 if (fileExists(filename))
731 #if defined(CREATE_SPECIAL_EDITION)
735 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
737 /* 6th try: look for fallback artwork in old default artwork directory */
738 /* (needed to prevent errors when trying to access unused artwork files) */
739 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
740 if (fileExists(filename))
744 return NULL; /* cannot find specified artwork file anywhere */
747 char *getCustomMusicFilename(char *basename)
749 static char *filename = NULL;
750 boolean skip_setup_artwork = FALSE;
752 checked_free(filename);
754 basename = getCorrectedArtworkBasename(basename);
756 if (!gfx.override_level_music)
758 /* 1st try: look for special artwork in current level series directory */
759 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
760 if (fileExists(filename))
765 /* check if there is special artwork configured in level series config */
766 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
768 /* 2nd try: look for special artwork configured in level series config */
769 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
770 if (fileExists(filename))
775 /* take missing artwork configured in level set config from default */
776 skip_setup_artwork = TRUE;
780 if (!skip_setup_artwork)
782 /* 3rd try: look for special artwork in configured artwork directory */
783 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
784 if (fileExists(filename))
790 /* 4th try: look for default artwork in new default artwork directory */
791 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
792 if (fileExists(filename))
797 /* 5th try: look for default artwork in old default artwork directory */
798 filename = getPath2(options.music_directory, basename);
799 if (fileExists(filename))
802 #if defined(CREATE_SPECIAL_EDITION)
806 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
808 /* 6th try: look for fallback artwork in old default artwork directory */
809 /* (needed to prevent errors when trying to access unused artwork files) */
810 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
811 if (fileExists(filename))
815 return NULL; /* cannot find specified artwork file anywhere */
818 char *getCustomArtworkFilename(char *basename, int type)
820 if (type == ARTWORK_TYPE_GRAPHICS)
821 return getCustomImageFilename(basename);
822 else if (type == ARTWORK_TYPE_SOUNDS)
823 return getCustomSoundFilename(basename);
824 else if (type == ARTWORK_TYPE_MUSIC)
825 return getCustomMusicFilename(basename);
827 return UNDEFINED_FILENAME;
830 char *getCustomArtworkConfigFilename(int type)
832 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
835 char *getCustomArtworkLevelConfigFilename(int type)
837 static char *filename = NULL;
839 checked_free(filename);
841 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
846 char *getCustomMusicDirectory(void)
848 static char *directory = NULL;
849 boolean skip_setup_artwork = FALSE;
851 checked_free(directory);
853 if (!gfx.override_level_music)
855 /* 1st try: look for special artwork in current level series directory */
856 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
857 if (directoryExists(directory))
862 /* check if there is special artwork configured in level series config */
863 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
865 /* 2nd try: look for special artwork configured in level series config */
866 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
867 if (directoryExists(directory))
872 /* take missing artwork configured in level set config from default */
873 skip_setup_artwork = TRUE;
877 if (!skip_setup_artwork)
879 /* 3rd try: look for special artwork in configured artwork directory */
880 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
881 if (directoryExists(directory))
887 /* 4th try: look for default artwork in new default artwork directory */
888 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
889 if (directoryExists(directory))
894 /* 5th try: look for default artwork in old default artwork directory */
895 directory = getStringCopy(options.music_directory);
896 if (directoryExists(directory))
899 return NULL; /* cannot find specified artwork file anywhere */
902 void InitTapeDirectory(char *level_subdir)
904 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
905 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
906 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
909 void InitScoreDirectory(char *level_subdir)
911 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
912 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
913 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
916 static void SaveUserLevelInfo();
918 void InitUserLevelDirectory(char *level_subdir)
920 if (!directoryExists(getUserLevelDir(level_subdir)))
922 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
923 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
924 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
930 void InitLevelSetupDirectory(char *level_subdir)
932 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
933 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
934 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
937 void InitCacheDirectory()
939 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
940 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
944 /* ------------------------------------------------------------------------- */
945 /* some functions to handle lists of level and artwork directories */
946 /* ------------------------------------------------------------------------- */
948 TreeInfo *newTreeInfo()
950 return checked_calloc(sizeof(TreeInfo));
953 TreeInfo *newTreeInfo_setDefaults(int type)
955 TreeInfo *ti = newTreeInfo();
957 setTreeInfoToDefaults(ti, type);
962 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
964 node_new->next = *node_first;
965 *node_first = node_new;
968 int numTreeInfo(TreeInfo *node)
981 boolean validLevelSeries(TreeInfo *node)
983 return (node != NULL && !node->node_group && !node->parent_link);
986 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
991 if (node->node_group) /* enter level group (step down into tree) */
992 return getFirstValidTreeInfoEntry(node->node_group);
993 else if (node->parent_link) /* skip start entry of level group */
995 if (node->next) /* get first real level series entry */
996 return getFirstValidTreeInfoEntry(node->next);
997 else /* leave empty level group and go on */
998 return getFirstValidTreeInfoEntry(node->node_parent->next);
1000 else /* this seems to be a regular level series */
1004 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1009 if (node->node_parent == NULL) /* top level group */
1010 return *node->node_top;
1011 else /* sub level group */
1012 return node->node_parent->node_group;
1015 int numTreeInfoInGroup(TreeInfo *node)
1017 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1020 int posTreeInfo(TreeInfo *node)
1022 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1027 if (node_cmp == node)
1031 node_cmp = node_cmp->next;
1037 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1039 TreeInfo *node_default = node;
1051 return node_default;
1054 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1056 if (identifier == NULL)
1061 if (node->node_group)
1063 TreeInfo *node_group;
1065 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1070 else if (!node->parent_link)
1072 if (strEqual(identifier, node->identifier))
1082 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1083 TreeInfo *node, boolean skip_sets_without_levels)
1090 if (!node->parent_link && !node->level_group &&
1091 skip_sets_without_levels && node->levels == 0)
1092 return cloneTreeNode(node_top, node_parent, node->next,
1093 skip_sets_without_levels);
1095 node_new = getTreeInfoCopy(node); /* copy complete node */
1097 node_new->node_top = node_top; /* correct top node link */
1098 node_new->node_parent = node_parent; /* correct parent node link */
1100 if (node->level_group)
1101 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1102 skip_sets_without_levels);
1104 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1105 skip_sets_without_levels);
1110 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1112 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1114 *ti_new = ti_cloned;
1117 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1119 boolean settings_changed = FALSE;
1123 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1124 !strEqual(node->graphics_set, node->graphics_set_ecs))
1126 setString(&node->graphics_set, node->graphics_set_ecs);
1127 settings_changed = TRUE;
1129 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1130 !strEqual(node->graphics_set, node->graphics_set_aga))
1132 setString(&node->graphics_set, node->graphics_set_aga);
1133 settings_changed = TRUE;
1136 if (node->node_group != NULL)
1137 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1142 return settings_changed;
1145 void dumpTreeInfo(TreeInfo *node, int depth)
1149 printf("Dumping TreeInfo:\n");
1153 for (i = 0; i < (depth + 1) * 3; i++)
1156 printf("'%s' / '%s'\n", node->identifier, node->name);
1159 // use for dumping artwork info tree
1160 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1161 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1164 if (node->node_group != NULL)
1165 dumpTreeInfo(node->node_group, depth + 1);
1171 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1172 int (*compare_function)(const void *,
1175 int num_nodes = numTreeInfo(*node_first);
1176 TreeInfo **sort_array;
1177 TreeInfo *node = *node_first;
1183 /* allocate array for sorting structure pointers */
1184 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1186 /* writing structure pointers to sorting array */
1187 while (i < num_nodes && node) /* double boundary check... */
1189 sort_array[i] = node;
1195 /* sorting the structure pointers in the sorting array */
1196 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1199 /* update the linkage of list elements with the sorted node array */
1200 for (i = 0; i < num_nodes - 1; i++)
1201 sort_array[i]->next = sort_array[i + 1];
1202 sort_array[num_nodes - 1]->next = NULL;
1204 /* update the linkage of the main list anchor pointer */
1205 *node_first = sort_array[0];
1209 /* now recursively sort the level group structures */
1213 if (node->node_group != NULL)
1214 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1220 void sortTreeInfo(TreeInfo **node_first)
1222 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1226 /* ========================================================================= */
1227 /* some stuff from "files.c" */
1228 /* ========================================================================= */
1230 #if defined(PLATFORM_WIN32)
1232 #define S_IRGRP S_IRUSR
1235 #define S_IROTH S_IRUSR
1238 #define S_IWGRP S_IWUSR
1241 #define S_IWOTH S_IWUSR
1244 #define S_IXGRP S_IXUSR
1247 #define S_IXOTH S_IXUSR
1250 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1255 #endif /* PLATFORM_WIN32 */
1257 /* file permissions for newly written files */
1258 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1259 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1260 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1262 #define MODE_W_PRIVATE (S_IWUSR)
1263 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1264 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1266 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1267 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1269 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1270 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1274 static char *dir = NULL;
1276 #if defined(PLATFORM_WIN32)
1279 dir = checked_malloc(MAX_PATH + 1);
1281 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1284 #elif defined(PLATFORM_UNIX)
1287 if ((dir = getenv("HOME")) == NULL)
1291 if ((pwd = getpwuid(getuid())) != NULL)
1292 dir = getStringCopy(pwd->pw_dir);
1304 char *getCommonDataDir(void)
1306 static char *common_data_dir = NULL;
1308 #if defined(PLATFORM_WIN32)
1309 if (common_data_dir == NULL)
1311 char *dir = checked_malloc(MAX_PATH + 1);
1313 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1314 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1315 common_data_dir = getPath2(dir, program.userdata_subdir);
1317 common_data_dir = options.rw_base_directory;
1320 if (common_data_dir == NULL)
1321 common_data_dir = options.rw_base_directory;
1324 return common_data_dir;
1327 char *getPersonalDataDir(void)
1329 static char *personal_data_dir = NULL;
1331 #if defined(PLATFORM_MACOSX)
1332 if (personal_data_dir == NULL)
1333 personal_data_dir = getPath2(getHomeDir(), "Documents");
1335 if (personal_data_dir == NULL)
1336 personal_data_dir = getHomeDir();
1339 return personal_data_dir;
1342 char *getUserGameDataDir(void)
1344 static char *user_game_data_dir = NULL;
1346 #if defined(PLATFORM_ANDROID)
1347 if (user_game_data_dir == NULL)
1348 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1350 if (user_game_data_dir == NULL)
1351 user_game_data_dir = getPath2(getPersonalDataDir(),
1352 program.userdata_subdir);
1355 return user_game_data_dir;
1358 void updateUserGameDataDir()
1360 #if defined(PLATFORM_MACOSX)
1361 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1362 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1364 /* convert old Unix style game data directory to Mac OS X style, if needed */
1365 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1367 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1369 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1370 userdata_dir_old, userdata_dir_new);
1372 /* continue using Unix style data directory -- this should not happen */
1373 program.userdata_path = getPath2(getPersonalDataDir(),
1374 program.userdata_subdir_unix);
1378 free(userdata_dir_old);
1384 return getUserGameDataDir();
1387 static mode_t posix_umask(mode_t mask)
1389 #if defined(PLATFORM_UNIX)
1396 static int posix_mkdir(const char *pathname, mode_t mode)
1398 #if defined(PLATFORM_WIN32)
1399 return mkdir(pathname);
1401 return mkdir(pathname, mode);
1405 static boolean posix_process_running_setgid()
1407 #if defined(PLATFORM_UNIX)
1408 return (getgid() != getegid());
1414 void createDirectory(char *dir, char *text, int permission_class)
1416 /* leave "other" permissions in umask untouched, but ensure group parts
1417 of USERDATA_DIR_MODE are not masked */
1418 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1419 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1420 mode_t last_umask = posix_umask(0);
1421 mode_t group_umask = ~(dir_mode & S_IRWXG);
1422 int running_setgid = posix_process_running_setgid();
1424 /* if we're setgid, protect files against "other" */
1425 /* else keep umask(0) to make the dir world-writable */
1428 posix_umask(last_umask & group_umask);
1430 dir_mode |= MODE_W_ALL;
1432 if (!directoryExists(dir))
1433 if (posix_mkdir(dir, dir_mode) != 0)
1434 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1435 text, dir, strerror(errno));
1437 if (permission_class == PERMS_PUBLIC && !running_setgid)
1438 chmod(dir, dir_mode);
1440 posix_umask(last_umask); /* restore previous umask */
1443 void InitUserDataDirectory()
1445 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1448 void SetFilePermissions(char *filename, int permission_class)
1450 int running_setgid = posix_process_running_setgid();
1451 int perms = (permission_class == PERMS_PRIVATE ?
1452 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1454 if (permission_class == PERMS_PUBLIC && !running_setgid)
1455 perms |= MODE_W_ALL;
1457 chmod(filename, perms);
1460 char *getCookie(char *file_type)
1462 static char cookie[MAX_COOKIE_LEN + 1];
1464 if (strlen(program.cookie_prefix) + 1 +
1465 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1466 return "[COOKIE ERROR]"; /* should never happen */
1468 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1469 program.cookie_prefix, file_type,
1470 program.version_major, program.version_minor);
1475 int getFileVersionFromCookieString(const char *cookie)
1477 const char *ptr_cookie1, *ptr_cookie2;
1478 const char *pattern1 = "_FILE_VERSION_";
1479 const char *pattern2 = "?.?";
1480 const int len_cookie = strlen(cookie);
1481 const int len_pattern1 = strlen(pattern1);
1482 const int len_pattern2 = strlen(pattern2);
1483 const int len_pattern = len_pattern1 + len_pattern2;
1484 int version_major, version_minor;
1486 if (len_cookie <= len_pattern)
1489 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1490 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1492 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1495 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1496 ptr_cookie2[1] != '.' ||
1497 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1500 version_major = ptr_cookie2[0] - '0';
1501 version_minor = ptr_cookie2[2] - '0';
1503 return VERSION_IDENT(version_major, version_minor, 0, 0);
1506 boolean checkCookieString(const char *cookie, const char *template)
1508 const char *pattern = "_FILE_VERSION_?.?";
1509 const int len_cookie = strlen(cookie);
1510 const int len_template = strlen(template);
1511 const int len_pattern = strlen(pattern);
1513 if (len_cookie != len_template)
1516 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1522 /* ------------------------------------------------------------------------- */
1523 /* setup file list and hash handling functions */
1524 /* ------------------------------------------------------------------------- */
1526 char *getFormattedSetupEntry(char *token, char *value)
1529 static char entry[MAX_LINE_LEN];
1531 /* if value is an empty string, just return token without value */
1535 /* start with the token and some spaces to format output line */
1536 sprintf(entry, "%s:", token);
1537 for (i = strlen(entry); i < token_value_position; i++)
1540 /* continue with the token's value */
1541 strcat(entry, value);
1546 SetupFileList *newSetupFileList(char *token, char *value)
1548 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1550 new->token = getStringCopy(token);
1551 new->value = getStringCopy(value);
1558 void freeSetupFileList(SetupFileList *list)
1563 checked_free(list->token);
1564 checked_free(list->value);
1567 freeSetupFileList(list->next);
1572 char *getListEntry(SetupFileList *list, char *token)
1577 if (strEqual(list->token, token))
1580 return getListEntry(list->next, token);
1583 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1588 if (strEqual(list->token, token))
1590 checked_free(list->value);
1592 list->value = getStringCopy(value);
1596 else if (list->next == NULL)
1597 return (list->next = newSetupFileList(token, value));
1599 return setListEntry(list->next, token, value);
1602 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1607 if (list->next == NULL)
1608 return (list->next = newSetupFileList(token, value));
1610 return addListEntry(list->next, token, value);
1613 #if ENABLE_UNUSED_CODE
1615 static void printSetupFileList(SetupFileList *list)
1620 printf("token: '%s'\n", list->token);
1621 printf("value: '%s'\n", list->value);
1623 printSetupFileList(list->next);
1629 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1630 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1631 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1632 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1634 #define insert_hash_entry hashtable_insert
1635 #define search_hash_entry hashtable_search
1636 #define change_hash_entry hashtable_change
1637 #define remove_hash_entry hashtable_remove
1640 unsigned int get_hash_from_key(void *key)
1645 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1646 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1647 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1648 it works better than many other constants, prime or not) has never been
1649 adequately explained.
1651 If you just want to have a good hash function, and cannot wait, djb2
1652 is one of the best string hash functions i know. It has excellent
1653 distribution and speed on many different sets of keys and table sizes.
1654 You are not likely to do better with one of the "well known" functions
1655 such as PJW, K&R, etc.
1657 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1660 char *str = (char *)key;
1661 unsigned int hash = 5381;
1664 while ((c = *str++))
1665 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1670 static int keys_are_equal(void *key1, void *key2)
1672 return (strEqual((char *)key1, (char *)key2));
1675 SetupFileHash *newSetupFileHash()
1677 SetupFileHash *new_hash =
1678 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1680 if (new_hash == NULL)
1681 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1686 void freeSetupFileHash(SetupFileHash *hash)
1691 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1694 char *getHashEntry(SetupFileHash *hash, char *token)
1699 return search_hash_entry(hash, token);
1702 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1709 value_copy = getStringCopy(value);
1711 /* change value; if it does not exist, insert it as new */
1712 if (!change_hash_entry(hash, token, value_copy))
1713 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1714 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1717 char *removeHashEntry(SetupFileHash *hash, char *token)
1722 return remove_hash_entry(hash, token);
1725 #if ENABLE_UNUSED_CODE
1727 static void printSetupFileHash(SetupFileHash *hash)
1729 BEGIN_HASH_ITERATION(hash, itr)
1731 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1732 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1734 END_HASH_ITERATION(hash, itr)
1739 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1740 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1741 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1743 static boolean token_value_separator_found = FALSE;
1744 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1745 static boolean token_value_separator_warning = FALSE;
1747 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1748 static boolean token_already_exists_warning = FALSE;
1751 static boolean getTokenValueFromSetupLineExt(char *line,
1752 char **token_ptr, char **value_ptr,
1753 char *filename, char *line_raw,
1755 boolean separator_required)
1757 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1758 char *token, *value, *line_ptr;
1760 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1761 if (line_raw == NULL)
1763 strncpy(line_copy, line, MAX_LINE_LEN);
1764 line_copy[MAX_LINE_LEN] = '\0';
1767 strcpy(line_raw_copy, line_copy);
1768 line_raw = line_raw_copy;
1771 /* cut trailing comment from input line */
1772 for (line_ptr = line; *line_ptr; line_ptr++)
1774 if (*line_ptr == '#')
1781 /* cut trailing whitespaces from input line */
1782 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1783 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1786 /* ignore empty lines */
1790 /* cut leading whitespaces from token */
1791 for (token = line; *token; token++)
1792 if (*token != ' ' && *token != '\t')
1795 /* start with empty value as reliable default */
1798 token_value_separator_found = FALSE;
1800 /* find end of token to determine start of value */
1801 for (line_ptr = token; *line_ptr; line_ptr++)
1803 /* first look for an explicit token/value separator, like ':' or '=' */
1804 if (*line_ptr == ':' || *line_ptr == '=')
1806 *line_ptr = '\0'; /* terminate token string */
1807 value = line_ptr + 1; /* set beginning of value */
1809 token_value_separator_found = TRUE;
1815 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1816 /* fallback: if no token/value separator found, also allow whitespaces */
1817 if (!token_value_separator_found && !separator_required)
1819 for (line_ptr = token; *line_ptr; line_ptr++)
1821 if (*line_ptr == ' ' || *line_ptr == '\t')
1823 *line_ptr = '\0'; /* terminate token string */
1824 value = line_ptr + 1; /* set beginning of value */
1826 token_value_separator_found = TRUE;
1832 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1833 if (token_value_separator_found)
1835 if (!token_value_separator_warning)
1837 Error(ERR_INFO_LINE, "-");
1839 if (filename != NULL)
1841 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1842 Error(ERR_INFO, "- config file: '%s'", filename);
1846 Error(ERR_WARN, "missing token/value separator(s):");
1849 token_value_separator_warning = TRUE;
1852 if (filename != NULL)
1853 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1855 Error(ERR_INFO, "- line: '%s'", line_raw);
1861 /* cut trailing whitespaces from token */
1862 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1863 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1866 /* cut leading whitespaces from value */
1867 for (; *value; value++)
1868 if (*value != ' ' && *value != '\t')
1877 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1879 /* while the internal (old) interface does not require a token/value
1880 separator (for downwards compatibility with existing files which
1881 don't use them), it is mandatory for the external (new) interface */
1883 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1886 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1887 boolean top_recursion_level, boolean is_hash)
1889 static SetupFileHash *include_filename_hash = NULL;
1890 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1891 char *token, *value, *line_ptr;
1892 void *insert_ptr = NULL;
1893 boolean read_continued_line = FALSE;
1895 int line_nr = 0, token_count = 0, include_count = 0;
1897 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1898 token_value_separator_warning = FALSE;
1901 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1902 token_already_exists_warning = FALSE;
1905 if (!(file = openFile(filename, MODE_READ)))
1907 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1912 /* use "insert pointer" to store list end for constant insertion complexity */
1914 insert_ptr = setup_file_data;
1916 /* on top invocation, create hash to mark included files (to prevent loops) */
1917 if (top_recursion_level)
1918 include_filename_hash = newSetupFileHash();
1920 /* mark this file as already included (to prevent including it again) */
1921 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1923 while (!checkEndOfFile(file))
1925 /* read next line of input file */
1926 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1929 /* check if line was completely read and is terminated by line break */
1930 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1933 /* cut trailing line break (this can be newline and/or carriage return) */
1934 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1935 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1938 /* copy raw input line for later use (mainly debugging output) */
1939 strcpy(line_raw, line);
1941 if (read_continued_line)
1943 /* append new line to existing line, if there is enough space */
1944 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1945 strcat(previous_line, line_ptr);
1947 strcpy(line, previous_line); /* copy storage buffer to line */
1949 read_continued_line = FALSE;
1952 /* if the last character is '\', continue at next line */
1953 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1955 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1956 strcpy(previous_line, line); /* copy line to storage buffer */
1958 read_continued_line = TRUE;
1963 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1964 line_raw, line_nr, FALSE))
1969 if (strEqual(token, "include"))
1971 if (getHashEntry(include_filename_hash, value) == NULL)
1973 char *basepath = getBasePath(filename);
1974 char *basename = getBaseName(value);
1975 char *filename_include = getPath2(basepath, basename);
1977 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1981 free(filename_include);
1987 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1994 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1996 getHashEntry((SetupFileHash *)setup_file_data, token);
1998 if (old_value != NULL)
2000 if (!token_already_exists_warning)
2002 Error(ERR_INFO_LINE, "-");
2003 Error(ERR_WARN, "duplicate token(s) found in config file:");
2004 Error(ERR_INFO, "- config file: '%s'", filename);
2006 token_already_exists_warning = TRUE;
2009 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2010 Error(ERR_INFO, " old value: '%s'", old_value);
2011 Error(ERR_INFO, " new value: '%s'", value);
2015 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2019 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2029 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2030 if (token_value_separator_warning)
2031 Error(ERR_INFO_LINE, "-");
2034 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2035 if (token_already_exists_warning)
2036 Error(ERR_INFO_LINE, "-");
2039 if (token_count == 0 && include_count == 0)
2040 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2042 if (top_recursion_level)
2043 freeSetupFileHash(include_filename_hash);
2048 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2052 if (!(file = fopen(filename, MODE_WRITE)))
2054 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2059 BEGIN_HASH_ITERATION(hash, itr)
2061 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2062 HASH_ITERATION_VALUE(itr)));
2064 END_HASH_ITERATION(hash, itr)
2069 SetupFileList *loadSetupFileList(char *filename)
2071 SetupFileList *setup_file_list = newSetupFileList("", "");
2072 SetupFileList *first_valid_list_entry;
2074 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2076 freeSetupFileList(setup_file_list);
2081 first_valid_list_entry = setup_file_list->next;
2083 /* free empty list header */
2084 setup_file_list->next = NULL;
2085 freeSetupFileList(setup_file_list);
2087 return first_valid_list_entry;
2090 SetupFileHash *loadSetupFileHash(char *filename)
2092 SetupFileHash *setup_file_hash = newSetupFileHash();
2094 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2096 freeSetupFileHash(setup_file_hash);
2101 return setup_file_hash;
2104 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2105 char *filename, char *identifier)
2107 #if USE_FILE_IDENTIFIERS
2108 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2111 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2112 else if (!checkCookieString(value, identifier))
2113 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2118 /* ========================================================================= */
2119 /* setup file stuff */
2120 /* ========================================================================= */
2122 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2123 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2124 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2126 /* level directory info */
2127 #define LEVELINFO_TOKEN_IDENTIFIER 0
2128 #define LEVELINFO_TOKEN_NAME 1
2129 #define LEVELINFO_TOKEN_NAME_SORTING 2
2130 #define LEVELINFO_TOKEN_AUTHOR 3
2131 #define LEVELINFO_TOKEN_YEAR 4
2132 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2133 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2134 #define LEVELINFO_TOKEN_TESTED_BY 7
2135 #define LEVELINFO_TOKEN_LEVELS 8
2136 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2137 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2138 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2139 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2140 #define LEVELINFO_TOKEN_READONLY 13
2141 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2142 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2143 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2144 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2145 #define LEVELINFO_TOKEN_MUSIC_SET 18
2146 #define LEVELINFO_TOKEN_FILENAME 19
2147 #define LEVELINFO_TOKEN_FILETYPE 20
2148 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2149 #define LEVELINFO_TOKEN_HANDICAP 22
2150 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2152 #define NUM_LEVELINFO_TOKENS 24
2154 static LevelDirTree ldi;
2156 static struct TokenInfo levelinfo_tokens[] =
2158 /* level directory info */
2159 { TYPE_STRING, &ldi.identifier, "identifier" },
2160 { TYPE_STRING, &ldi.name, "name" },
2161 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2162 { TYPE_STRING, &ldi.author, "author" },
2163 { TYPE_STRING, &ldi.year, "year" },
2164 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2165 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2166 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2167 { TYPE_INTEGER, &ldi.levels, "levels" },
2168 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2169 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2170 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2171 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2172 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2173 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2174 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2175 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2176 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2177 { TYPE_STRING, &ldi.music_set, "music_set" },
2178 { TYPE_STRING, &ldi.level_filename, "filename" },
2179 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2180 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2181 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2182 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2185 static struct TokenInfo artworkinfo_tokens[] =
2187 /* artwork directory info */
2188 { TYPE_STRING, &ldi.identifier, "identifier" },
2189 { TYPE_STRING, &ldi.subdir, "subdir" },
2190 { TYPE_STRING, &ldi.name, "name" },
2191 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2192 { TYPE_STRING, &ldi.author, "author" },
2193 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2194 { TYPE_STRING, &ldi.basepath, "basepath" },
2195 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2196 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2197 { TYPE_INTEGER, &ldi.color, "color" },
2198 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2203 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2207 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2208 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2209 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2210 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2213 ti->node_parent = NULL;
2214 ti->node_group = NULL;
2221 ti->fullpath = NULL;
2222 ti->basepath = NULL;
2223 ti->identifier = NULL;
2224 ti->name = getStringCopy(ANONYMOUS_NAME);
2225 ti->name_sorting = NULL;
2226 ti->author = getStringCopy(ANONYMOUS_NAME);
2229 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2230 ti->latest_engine = FALSE; /* default: get from level */
2231 ti->parent_link = FALSE;
2232 ti->in_user_dir = FALSE;
2233 ti->user_defined = FALSE;
2235 ti->class_desc = NULL;
2237 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2239 if (ti->type == TREE_TYPE_LEVEL_DIR)
2241 ti->imported_from = NULL;
2242 ti->imported_by = NULL;
2243 ti->tested_by = NULL;
2245 ti->graphics_set_ecs = NULL;
2246 ti->graphics_set_aga = NULL;
2247 ti->graphics_set = NULL;
2248 ti->sounds_set = NULL;
2249 ti->music_set = NULL;
2250 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2251 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2252 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2254 ti->level_filename = NULL;
2255 ti->level_filetype = NULL;
2257 ti->special_flags = NULL;
2260 ti->first_level = 0;
2262 ti->level_group = FALSE;
2263 ti->handicap_level = 0;
2264 ti->readonly = TRUE;
2265 ti->handicap = TRUE;
2266 ti->skip_levels = FALSE;
2270 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2274 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2276 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2281 /* copy all values from the parent structure */
2283 ti->type = parent->type;
2285 ti->node_top = parent->node_top;
2286 ti->node_parent = parent;
2287 ti->node_group = NULL;
2294 ti->fullpath = NULL;
2295 ti->basepath = NULL;
2296 ti->identifier = NULL;
2297 ti->name = getStringCopy(ANONYMOUS_NAME);
2298 ti->name_sorting = NULL;
2299 ti->author = getStringCopy(parent->author);
2300 ti->year = getStringCopy(parent->year);
2302 ti->sort_priority = parent->sort_priority;
2303 ti->latest_engine = parent->latest_engine;
2304 ti->parent_link = FALSE;
2305 ti->in_user_dir = parent->in_user_dir;
2306 ti->user_defined = parent->user_defined;
2307 ti->color = parent->color;
2308 ti->class_desc = getStringCopy(parent->class_desc);
2310 ti->infotext = getStringCopy(parent->infotext);
2312 if (ti->type == TREE_TYPE_LEVEL_DIR)
2314 ti->imported_from = getStringCopy(parent->imported_from);
2315 ti->imported_by = getStringCopy(parent->imported_by);
2316 ti->tested_by = getStringCopy(parent->tested_by);
2318 ti->graphics_set_ecs = NULL;
2319 ti->graphics_set_aga = NULL;
2320 ti->graphics_set = NULL;
2321 ti->sounds_set = NULL;
2322 ti->music_set = NULL;
2323 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2324 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2325 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2327 ti->level_filename = NULL;
2328 ti->level_filetype = NULL;
2330 ti->special_flags = getStringCopy(parent->special_flags);
2333 ti->first_level = 0;
2335 ti->level_group = FALSE;
2336 ti->handicap_level = 0;
2337 ti->readonly = parent->readonly;
2338 ti->handicap = TRUE;
2339 ti->skip_levels = FALSE;
2343 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2345 TreeInfo *ti_copy = newTreeInfo();
2347 /* copy all values from the original structure */
2349 ti_copy->type = ti->type;
2351 ti_copy->node_top = ti->node_top;
2352 ti_copy->node_parent = ti->node_parent;
2353 ti_copy->node_group = ti->node_group;
2354 ti_copy->next = ti->next;
2356 ti_copy->cl_first = ti->cl_first;
2357 ti_copy->cl_cursor = ti->cl_cursor;
2359 ti_copy->subdir = getStringCopy(ti->subdir);
2360 ti_copy->fullpath = getStringCopy(ti->fullpath);
2361 ti_copy->basepath = getStringCopy(ti->basepath);
2362 ti_copy->identifier = getStringCopy(ti->identifier);
2363 ti_copy->name = getStringCopy(ti->name);
2364 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2365 ti_copy->author = getStringCopy(ti->author);
2366 ti_copy->year = getStringCopy(ti->year);
2367 ti_copy->imported_from = getStringCopy(ti->imported_from);
2368 ti_copy->imported_by = getStringCopy(ti->imported_by);
2369 ti_copy->tested_by = getStringCopy(ti->tested_by);
2371 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2372 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2373 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2374 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2375 ti_copy->music_set = getStringCopy(ti->music_set);
2376 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2377 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2378 ti_copy->music_path = getStringCopy(ti->music_path);
2380 ti_copy->level_filename = getStringCopy(ti->level_filename);
2381 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2383 ti_copy->special_flags = getStringCopy(ti->special_flags);
2385 ti_copy->levels = ti->levels;
2386 ti_copy->first_level = ti->first_level;
2387 ti_copy->last_level = ti->last_level;
2388 ti_copy->sort_priority = ti->sort_priority;
2390 ti_copy->latest_engine = ti->latest_engine;
2392 ti_copy->level_group = ti->level_group;
2393 ti_copy->parent_link = ti->parent_link;
2394 ti_copy->in_user_dir = ti->in_user_dir;
2395 ti_copy->user_defined = ti->user_defined;
2396 ti_copy->readonly = ti->readonly;
2397 ti_copy->handicap = ti->handicap;
2398 ti_copy->skip_levels = ti->skip_levels;
2400 ti_copy->color = ti->color;
2401 ti_copy->class_desc = getStringCopy(ti->class_desc);
2402 ti_copy->handicap_level = ti->handicap_level;
2404 ti_copy->infotext = getStringCopy(ti->infotext);
2409 void freeTreeInfo(TreeInfo *ti)
2414 checked_free(ti->subdir);
2415 checked_free(ti->fullpath);
2416 checked_free(ti->basepath);
2417 checked_free(ti->identifier);
2419 checked_free(ti->name);
2420 checked_free(ti->name_sorting);
2421 checked_free(ti->author);
2422 checked_free(ti->year);
2424 checked_free(ti->class_desc);
2426 checked_free(ti->infotext);
2428 if (ti->type == TREE_TYPE_LEVEL_DIR)
2430 checked_free(ti->imported_from);
2431 checked_free(ti->imported_by);
2432 checked_free(ti->tested_by);
2434 checked_free(ti->graphics_set_ecs);
2435 checked_free(ti->graphics_set_aga);
2436 checked_free(ti->graphics_set);
2437 checked_free(ti->sounds_set);
2438 checked_free(ti->music_set);
2440 checked_free(ti->graphics_path);
2441 checked_free(ti->sounds_path);
2442 checked_free(ti->music_path);
2444 checked_free(ti->level_filename);
2445 checked_free(ti->level_filetype);
2447 checked_free(ti->special_flags);
2450 // recursively free child node
2452 freeTreeInfo(ti->node_group);
2454 // recursively free next node
2456 freeTreeInfo(ti->next);
2461 void setSetupInfo(struct TokenInfo *token_info,
2462 int token_nr, char *token_value)
2464 int token_type = token_info[token_nr].type;
2465 void *setup_value = token_info[token_nr].value;
2467 if (token_value == NULL)
2470 /* set setup field to corresponding token value */
2475 *(boolean *)setup_value = get_boolean_from_string(token_value);
2479 *(int *)setup_value = get_switch3_from_string(token_value);
2483 *(Key *)setup_value = getKeyFromKeyName(token_value);
2487 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2491 *(int *)setup_value = get_integer_from_string(token_value);
2495 checked_free(*(char **)setup_value);
2496 *(char **)setup_value = getStringCopy(token_value);
2504 static int compareTreeInfoEntries(const void *object1, const void *object2)
2506 const TreeInfo *entry1 = *((TreeInfo **)object1);
2507 const TreeInfo *entry2 = *((TreeInfo **)object2);
2508 int class_sorting1 = 0, class_sorting2 = 0;
2511 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2513 class_sorting1 = LEVELSORTING(entry1);
2514 class_sorting2 = LEVELSORTING(entry2);
2516 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2517 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2518 entry1->type == TREE_TYPE_MUSIC_DIR)
2520 class_sorting1 = ARTWORKSORTING(entry1);
2521 class_sorting2 = ARTWORKSORTING(entry2);
2524 if (entry1->parent_link || entry2->parent_link)
2525 compare_result = (entry1->parent_link ? -1 : +1);
2526 else if (entry1->sort_priority == entry2->sort_priority)
2528 char *name1 = getStringToLower(entry1->name_sorting);
2529 char *name2 = getStringToLower(entry2->name_sorting);
2531 compare_result = strcmp(name1, name2);
2536 else if (class_sorting1 == class_sorting2)
2537 compare_result = entry1->sort_priority - entry2->sort_priority;
2539 compare_result = class_sorting1 - class_sorting2;
2541 return compare_result;
2544 static void createParentTreeInfoNode(TreeInfo *node_parent)
2548 if (node_parent == NULL)
2551 ti_new = newTreeInfo();
2552 setTreeInfoToDefaults(ti_new, node_parent->type);
2554 ti_new->node_parent = node_parent;
2555 ti_new->parent_link = TRUE;
2557 setString(&ti_new->identifier, node_parent->identifier);
2558 setString(&ti_new->name, ".. (parent directory)");
2559 setString(&ti_new->name_sorting, ti_new->name);
2561 setString(&ti_new->subdir, "..");
2562 setString(&ti_new->fullpath, node_parent->fullpath);
2564 ti_new->sort_priority = node_parent->sort_priority;
2565 ti_new->latest_engine = node_parent->latest_engine;
2567 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2569 pushTreeInfo(&node_parent->node_group, ti_new);
2573 /* -------------------------------------------------------------------------- */
2574 /* functions for handling level and custom artwork info cache */
2575 /* -------------------------------------------------------------------------- */
2577 static void LoadArtworkInfoCache()
2579 InitCacheDirectory();
2581 if (artworkinfo_cache_old == NULL)
2583 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2585 /* try to load artwork info hash from already existing cache file */
2586 artworkinfo_cache_old = loadSetupFileHash(filename);
2588 /* if no artwork info cache file was found, start with empty hash */
2589 if (artworkinfo_cache_old == NULL)
2590 artworkinfo_cache_old = newSetupFileHash();
2595 if (artworkinfo_cache_new == NULL)
2596 artworkinfo_cache_new = newSetupFileHash();
2599 static void SaveArtworkInfoCache()
2601 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2603 InitCacheDirectory();
2605 saveSetupFileHash(artworkinfo_cache_new, filename);
2610 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2612 static char *prefix = NULL;
2614 checked_free(prefix);
2616 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2621 /* (identical to above function, but separate string buffer needed -- nasty) */
2622 static char *getCacheToken(char *prefix, char *suffix)
2624 static char *token = NULL;
2626 checked_free(token);
2628 token = getStringCat2WithSeparator(prefix, suffix, ".");
2633 static char *getFileTimestampString(char *filename)
2635 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2638 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2640 struct stat file_status;
2642 if (timestamp_string == NULL)
2645 if (stat(filename, &file_status) != 0) /* cannot stat file */
2648 return (file_status.st_mtime != atoi(timestamp_string));
2651 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2653 char *identifier = level_node->subdir;
2654 char *type_string = ARTWORK_DIRECTORY(type);
2655 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2656 char *token_main = getCacheToken(token_prefix, "CACHED");
2657 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2658 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2659 TreeInfo *artwork_info = NULL;
2661 if (!use_artworkinfo_cache)
2668 artwork_info = newTreeInfo();
2669 setTreeInfoToDefaults(artwork_info, type);
2671 /* set all structure fields according to the token/value pairs */
2672 ldi = *artwork_info;
2673 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2675 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2676 char *value = getHashEntry(artworkinfo_cache_old, token);
2678 setSetupInfo(artworkinfo_tokens, i, value);
2680 /* check if cache entry for this item is invalid or incomplete */
2683 Error(ERR_WARN, "cache entry '%s' invalid", token);
2689 *artwork_info = ldi;
2694 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2695 LEVELINFO_FILENAME);
2696 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2697 ARTWORKINFO_FILENAME(type));
2699 /* check if corresponding "levelinfo.conf" file has changed */
2700 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2701 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2703 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2706 /* check if corresponding "<artworkinfo>.conf" file has changed */
2707 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2708 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2710 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2713 checked_free(filename_levelinfo);
2714 checked_free(filename_artworkinfo);
2717 if (!cached && artwork_info != NULL)
2719 freeTreeInfo(artwork_info);
2724 return artwork_info;
2727 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2728 LevelDirTree *level_node, int type)
2730 char *identifier = level_node->subdir;
2731 char *type_string = ARTWORK_DIRECTORY(type);
2732 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2733 char *token_main = getCacheToken(token_prefix, "CACHED");
2734 boolean set_cache_timestamps = TRUE;
2737 setHashEntry(artworkinfo_cache_new, token_main, "true");
2739 if (set_cache_timestamps)
2741 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2742 LEVELINFO_FILENAME);
2743 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2744 ARTWORKINFO_FILENAME(type));
2745 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2746 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2748 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2749 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2751 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2752 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2754 checked_free(filename_levelinfo);
2755 checked_free(filename_artworkinfo);
2756 checked_free(timestamp_levelinfo);
2757 checked_free(timestamp_artworkinfo);
2760 ldi = *artwork_info;
2761 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2763 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2764 char *value = getSetupValue(artworkinfo_tokens[i].type,
2765 artworkinfo_tokens[i].value);
2767 setHashEntry(artworkinfo_cache_new, token, value);
2772 /* -------------------------------------------------------------------------- */
2773 /* functions for loading level info and custom artwork info */
2774 /* -------------------------------------------------------------------------- */
2776 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2777 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2779 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2780 TreeInfo *node_parent,
2781 char *level_directory,
2782 char *directory_name)
2784 char *directory_path = getPath2(level_directory, directory_name);
2785 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2786 SetupFileHash *setup_file_hash;
2787 LevelDirTree *leveldir_new = NULL;
2790 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2791 if (!options.debug && !fileExists(filename))
2793 free(directory_path);
2799 setup_file_hash = loadSetupFileHash(filename);
2801 if (setup_file_hash == NULL)
2803 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2805 free(directory_path);
2811 leveldir_new = newTreeInfo();
2814 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2816 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2818 leveldir_new->subdir = getStringCopy(directory_name);
2820 checkSetupFileHashIdentifier(setup_file_hash, filename,
2821 getCookie("LEVELINFO"));
2823 /* set all structure fields according to the token/value pairs */
2824 ldi = *leveldir_new;
2825 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2826 setSetupInfo(levelinfo_tokens, i,
2827 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2828 *leveldir_new = ldi;
2830 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2831 setString(&leveldir_new->name, leveldir_new->subdir);
2833 if (leveldir_new->identifier == NULL)
2834 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2836 if (leveldir_new->name_sorting == NULL)
2837 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2839 if (node_parent == NULL) /* top level group */
2841 leveldir_new->basepath = getStringCopy(level_directory);
2842 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2844 else /* sub level group */
2846 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2847 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2850 leveldir_new->last_level =
2851 leveldir_new->first_level + leveldir_new->levels - 1;
2853 leveldir_new->in_user_dir =
2854 (!strEqual(leveldir_new->basepath, options.level_directory));
2856 /* adjust some settings if user's private level directory was detected */
2857 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2858 leveldir_new->in_user_dir &&
2859 (strEqual(leveldir_new->subdir, getLoginName()) ||
2860 strEqual(leveldir_new->name, getLoginName()) ||
2861 strEqual(leveldir_new->author, getRealName())))
2863 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2864 leveldir_new->readonly = FALSE;
2867 leveldir_new->user_defined =
2868 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2870 leveldir_new->color = LEVELCOLOR(leveldir_new);
2872 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2874 leveldir_new->handicap_level = /* set handicap to default value */
2875 (leveldir_new->user_defined || !leveldir_new->handicap ?
2876 leveldir_new->last_level : leveldir_new->first_level);
2878 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
2879 leveldir_new->level_group);
2881 pushTreeInfo(node_first, leveldir_new);
2883 freeSetupFileHash(setup_file_hash);
2885 if (leveldir_new->level_group)
2887 /* create node to link back to current level directory */
2888 createParentTreeInfoNode(leveldir_new);
2890 /* recursively step into sub-directory and look for more level series */
2891 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2892 leveldir_new, directory_path);
2895 free(directory_path);
2901 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2902 TreeInfo *node_parent,
2903 char *level_directory)
2906 DirectoryEntry *dir_entry;
2907 boolean valid_entry_found = FALSE;
2909 if ((dir = openDirectory(level_directory)) == NULL)
2911 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2916 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2918 char *directory_name = dir_entry->basename;
2919 char *directory_path = getPath2(level_directory, directory_name);
2921 /* skip entries for current and parent directory */
2922 if (strEqual(directory_name, ".") ||
2923 strEqual(directory_name, ".."))
2925 free(directory_path);
2930 /* find out if directory entry is itself a directory */
2931 if (!dir_entry->is_directory) /* not a directory */
2933 free(directory_path);
2938 free(directory_path);
2940 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2941 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2942 strEqual(directory_name, MUSIC_DIRECTORY))
2945 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2950 closeDirectory(dir);
2952 /* special case: top level directory may directly contain "levelinfo.conf" */
2953 if (node_parent == NULL && !valid_entry_found)
2955 /* check if this directory directly contains a file "levelinfo.conf" */
2956 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2957 level_directory, ".");
2960 if (!valid_entry_found)
2961 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2965 boolean AdjustGraphicsForEMC()
2967 boolean settings_changed = FALSE;
2969 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2970 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2972 return settings_changed;
2975 void LoadLevelInfo()
2977 InitUserLevelDirectory(getLoginName());
2979 DrawInitText("Loading level series", 120, FC_GREEN);
2981 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2982 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2984 /* after loading all level set information, clone the level directory tree
2985 and remove all level sets without levels (these may still contain artwork
2986 to be offered in the setup menu as "custom artwork", and are therefore
2987 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2988 leveldir_first_all = leveldir_first;
2989 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2991 AdjustGraphicsForEMC();
2993 /* before sorting, the first entries will be from the user directory */
2994 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2996 if (leveldir_first == NULL)
2997 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2999 sortTreeInfo(&leveldir_first);
3001 #if ENABLE_UNUSED_CODE
3002 dumpTreeInfo(leveldir_first, 0);
3006 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3007 TreeInfo *node_parent,
3008 char *base_directory,
3009 char *directory_name, int type)
3011 char *directory_path = getPath2(base_directory, directory_name);
3012 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3013 SetupFileHash *setup_file_hash = NULL;
3014 TreeInfo *artwork_new = NULL;
3017 if (fileExists(filename))
3018 setup_file_hash = loadSetupFileHash(filename);
3020 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3023 DirectoryEntry *dir_entry;
3024 boolean valid_file_found = FALSE;
3026 if ((dir = openDirectory(directory_path)) != NULL)
3028 while ((dir_entry = readDirectory(dir)) != NULL)
3030 char *entry_name = dir_entry->basename;
3032 if (FileIsArtworkType(entry_name, type))
3034 valid_file_found = TRUE;
3040 closeDirectory(dir);
3043 if (!valid_file_found)
3045 if (!strEqual(directory_name, "."))
3046 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3048 free(directory_path);
3055 artwork_new = newTreeInfo();
3058 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3060 setTreeInfoToDefaults(artwork_new, type);
3062 artwork_new->subdir = getStringCopy(directory_name);
3064 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3066 /* set all structure fields according to the token/value pairs */
3068 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3069 setSetupInfo(levelinfo_tokens, i,
3070 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3073 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3074 setString(&artwork_new->name, artwork_new->subdir);
3076 if (artwork_new->identifier == NULL)
3077 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3079 if (artwork_new->name_sorting == NULL)
3080 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3083 if (node_parent == NULL) /* top level group */
3085 artwork_new->basepath = getStringCopy(base_directory);
3086 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3088 else /* sub level group */
3090 artwork_new->basepath = getStringCopy(node_parent->basepath);
3091 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3094 artwork_new->in_user_dir =
3095 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3097 /* (may use ".sort_priority" from "setup_file_hash" above) */
3098 artwork_new->color = ARTWORKCOLOR(artwork_new);
3100 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3102 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3104 if (strEqual(artwork_new->subdir, "."))
3106 if (artwork_new->user_defined)
3108 setString(&artwork_new->identifier, "private");
3109 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3113 setString(&artwork_new->identifier, "classic");
3114 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3117 /* set to new values after changing ".sort_priority" */
3118 artwork_new->color = ARTWORKCOLOR(artwork_new);
3120 setString(&artwork_new->class_desc,
3121 getLevelClassDescription(artwork_new));
3125 setString(&artwork_new->identifier, artwork_new->subdir);
3128 setString(&artwork_new->name, artwork_new->identifier);
3129 setString(&artwork_new->name_sorting, artwork_new->name);
3132 pushTreeInfo(node_first, artwork_new);
3134 freeSetupFileHash(setup_file_hash);
3136 free(directory_path);
3142 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3143 TreeInfo *node_parent,
3144 char *base_directory, int type)
3147 DirectoryEntry *dir_entry;
3148 boolean valid_entry_found = FALSE;
3150 if ((dir = openDirectory(base_directory)) == NULL)
3152 /* display error if directory is main "options.graphics_directory" etc. */
3153 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3154 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3159 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3161 char *directory_name = dir_entry->basename;
3162 char *directory_path = getPath2(base_directory, directory_name);
3164 /* skip directory entries for current and parent directory */
3165 if (strEqual(directory_name, ".") ||
3166 strEqual(directory_name, ".."))
3168 free(directory_path);
3173 /* skip directory entries which are not a directory */
3174 if (!dir_entry->is_directory) /* not a directory */
3176 free(directory_path);
3181 free(directory_path);
3183 /* check if this directory contains artwork with or without config file */
3184 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3186 directory_name, type);
3189 closeDirectory(dir);
3191 /* check if this directory directly contains artwork itself */
3192 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3193 base_directory, ".",
3195 if (!valid_entry_found)
3196 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3200 static TreeInfo *getDummyArtworkInfo(int type)
3202 /* this is only needed when there is completely no artwork available */
3203 TreeInfo *artwork_new = newTreeInfo();
3205 setTreeInfoToDefaults(artwork_new, type);
3207 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3208 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3209 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3211 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3212 setString(&artwork_new->name, UNDEFINED_FILENAME);
3213 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3218 void LoadArtworkInfo()
3220 LoadArtworkInfoCache();
3222 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3224 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3225 options.graphics_directory,
3226 TREE_TYPE_GRAPHICS_DIR);
3227 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3228 getUserGraphicsDir(),
3229 TREE_TYPE_GRAPHICS_DIR);
3231 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3232 options.sounds_directory,
3233 TREE_TYPE_SOUNDS_DIR);
3234 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3236 TREE_TYPE_SOUNDS_DIR);
3238 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3239 options.music_directory,
3240 TREE_TYPE_MUSIC_DIR);
3241 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3243 TREE_TYPE_MUSIC_DIR);
3245 if (artwork.gfx_first == NULL)
3246 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3247 if (artwork.snd_first == NULL)
3248 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3249 if (artwork.mus_first == NULL)
3250 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3252 /* before sorting, the first entries will be from the user directory */
3253 artwork.gfx_current =
3254 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3255 if (artwork.gfx_current == NULL)
3256 artwork.gfx_current =
3257 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3258 if (artwork.gfx_current == NULL)
3259 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3261 artwork.snd_current =
3262 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3263 if (artwork.snd_current == NULL)
3264 artwork.snd_current =
3265 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3266 if (artwork.snd_current == NULL)
3267 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3269 artwork.mus_current =
3270 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3271 if (artwork.mus_current == NULL)
3272 artwork.mus_current =
3273 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3274 if (artwork.mus_current == NULL)
3275 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3277 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3278 artwork.snd_current_identifier = artwork.snd_current->identifier;
3279 artwork.mus_current_identifier = artwork.mus_current->identifier;
3281 #if ENABLE_UNUSED_CODE
3282 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3283 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3284 printf("music set == %s\n\n", artwork.mus_current_identifier);
3287 sortTreeInfo(&artwork.gfx_first);
3288 sortTreeInfo(&artwork.snd_first);
3289 sortTreeInfo(&artwork.mus_first);
3291 #if ENABLE_UNUSED_CODE
3292 dumpTreeInfo(artwork.gfx_first, 0);
3293 dumpTreeInfo(artwork.snd_first, 0);
3294 dumpTreeInfo(artwork.mus_first, 0);
3298 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3299 LevelDirTree *level_node)
3301 int type = (*artwork_node)->type;
3303 /* recursively check all level directories for artwork sub-directories */
3307 /* check all tree entries for artwork, but skip parent link entries */
3308 if (!level_node->parent_link)
3310 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3311 boolean cached = (artwork_new != NULL);
3315 pushTreeInfo(artwork_node, artwork_new);
3319 TreeInfo *topnode_last = *artwork_node;
3320 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3321 ARTWORK_DIRECTORY(type));
3323 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3325 if (topnode_last != *artwork_node) /* check for newly added node */
3327 artwork_new = *artwork_node;
3329 setString(&artwork_new->identifier, level_node->subdir);
3330 setString(&artwork_new->name, level_node->name);
3331 setString(&artwork_new->name_sorting, level_node->name_sorting);
3333 artwork_new->sort_priority = level_node->sort_priority;
3334 artwork_new->color = LEVELCOLOR(artwork_new);
3340 /* insert artwork info (from old cache or filesystem) into new cache */
3341 if (artwork_new != NULL)
3342 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3345 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3346 level_node->level_group);
3348 if (level_node->node_group != NULL)
3349 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3351 level_node = level_node->next;
3355 void LoadLevelArtworkInfo()
3357 print_timestamp_init("LoadLevelArtworkInfo");
3359 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3361 print_timestamp_time("DrawTimeText");
3363 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3364 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3365 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3366 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3367 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3368 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3370 SaveArtworkInfoCache();
3372 print_timestamp_time("SaveArtworkInfoCache");
3374 /* needed for reloading level artwork not known at ealier stage */
3376 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3378 artwork.gfx_current =
3379 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3380 if (artwork.gfx_current == NULL)
3381 artwork.gfx_current =
3382 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3383 if (artwork.gfx_current == NULL)
3384 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3387 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3389 artwork.snd_current =
3390 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3391 if (artwork.snd_current == NULL)
3392 artwork.snd_current =
3393 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3394 if (artwork.snd_current == NULL)
3395 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3398 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3400 artwork.mus_current =
3401 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3402 if (artwork.mus_current == NULL)
3403 artwork.mus_current =
3404 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3405 if (artwork.mus_current == NULL)
3406 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3409 print_timestamp_time("getTreeInfoFromIdentifier");
3411 sortTreeInfo(&artwork.gfx_first);
3412 sortTreeInfo(&artwork.snd_first);
3413 sortTreeInfo(&artwork.mus_first);
3415 print_timestamp_time("sortTreeInfo");
3417 #if ENABLE_UNUSED_CODE
3418 dumpTreeInfo(artwork.gfx_first, 0);
3419 dumpTreeInfo(artwork.snd_first, 0);
3420 dumpTreeInfo(artwork.mus_first, 0);
3423 print_timestamp_done("LoadLevelArtworkInfo");
3426 static void SaveUserLevelInfo()
3428 LevelDirTree *level_info;
3433 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3435 if (!(file = fopen(filename, MODE_WRITE)))
3437 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3442 level_info = newTreeInfo();
3444 /* always start with reliable default values */
3445 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3447 setString(&level_info->name, getLoginName());
3448 setString(&level_info->author, getRealName());
3449 level_info->levels = 100;
3450 level_info->first_level = 1;
3452 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3454 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3455 getCookie("LEVELINFO")));
3458 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3460 if (i == LEVELINFO_TOKEN_NAME ||
3461 i == LEVELINFO_TOKEN_AUTHOR ||
3462 i == LEVELINFO_TOKEN_LEVELS ||
3463 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3464 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3466 /* just to make things nicer :) */
3467 if (i == LEVELINFO_TOKEN_AUTHOR)
3468 fprintf(file, "\n");
3471 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3475 SetFilePermissions(filename, PERMS_PRIVATE);
3477 freeTreeInfo(level_info);
3481 char *getSetupValue(int type, void *value)
3483 static char value_string[MAX_LINE_LEN];
3491 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3495 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3499 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3500 *(int *)value == FALSE ? "off" : "on"));
3504 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3507 case TYPE_YES_NO_AUTO:
3508 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3509 *(int *)value == FALSE ? "no" : "yes"));
3513 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3517 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3521 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3525 sprintf(value_string, "%d", *(int *)value);
3529 if (*(char **)value == NULL)
3532 strcpy(value_string, *(char **)value);
3536 value_string[0] = '\0';
3540 if (type & TYPE_GHOSTED)
3541 strcpy(value_string, "n/a");
3543 return value_string;
3546 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3550 static char token_string[MAX_LINE_LEN];
3551 int token_type = token_info[token_nr].type;
3552 void *setup_value = token_info[token_nr].value;
3553 char *token_text = token_info[token_nr].text;
3554 char *value_string = getSetupValue(token_type, setup_value);
3556 /* build complete token string */
3557 sprintf(token_string, "%s%s", prefix, token_text);
3559 /* build setup entry line */
3560 line = getFormattedSetupEntry(token_string, value_string);
3562 if (token_type == TYPE_KEY_X11)
3564 Key key = *(Key *)setup_value;
3565 char *keyname = getKeyNameFromKey(key);
3567 /* add comment, if useful */
3568 if (!strEqual(keyname, "(undefined)") &&
3569 !strEqual(keyname, "(unknown)"))
3571 /* add at least one whitespace */
3573 for (i = strlen(line); i < token_comment_position; i++)
3577 strcat(line, keyname);
3584 void LoadLevelSetup_LastSeries()
3586 /* ----------------------------------------------------------------------- */
3587 /* ~/.<program>/levelsetup.conf */
3588 /* ----------------------------------------------------------------------- */
3590 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3591 SetupFileHash *level_setup_hash = NULL;
3593 /* always start with reliable default values */
3594 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3596 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3597 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3599 if (leveldir_current == NULL)
3600 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3603 if ((level_setup_hash = loadSetupFileHash(filename)))
3605 char *last_level_series =
3606 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3608 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3610 if (leveldir_current == NULL)
3611 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3613 checkSetupFileHashIdentifier(level_setup_hash, filename,
3614 getCookie("LEVELSETUP"));
3616 freeSetupFileHash(level_setup_hash);
3619 Error(ERR_WARN, "using default setup values");
3624 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3626 /* ----------------------------------------------------------------------- */
3627 /* ~/.<program>/levelsetup.conf */
3628 /* ----------------------------------------------------------------------- */
3630 // check if the current level directory structure is available at this point
3631 if (leveldir_current == NULL)
3634 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3635 char *level_subdir = leveldir_current->subdir;
3638 InitUserDataDirectory();
3640 if (!(file = fopen(filename, MODE_WRITE)))
3642 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3649 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3650 getCookie("LEVELSETUP")));
3652 if (deactivate_last_level_series)
3653 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3655 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3660 SetFilePermissions(filename, PERMS_PRIVATE);
3665 void SaveLevelSetup_LastSeries()
3667 SaveLevelSetup_LastSeries_Ext(FALSE);
3670 void SaveLevelSetup_LastSeries_Deactivate()
3672 SaveLevelSetup_LastSeries_Ext(TRUE);
3675 static void checkSeriesInfo()
3677 static char *level_directory = NULL;
3680 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3682 level_directory = getPath2((leveldir_current->in_user_dir ?
3683 getUserLevelDir(NULL) :
3684 options.level_directory),
3685 leveldir_current->fullpath);
3687 if ((dir = openDirectory(level_directory)) == NULL)
3689 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3694 closeDirectory(dir);
3697 void LoadLevelSetup_SeriesInfo()
3700 SetupFileHash *level_setup_hash = NULL;
3701 char *level_subdir = leveldir_current->subdir;
3704 /* always start with reliable default values */
3705 level_nr = leveldir_current->first_level;
3707 for (i = 0; i < MAX_LEVELS; i++)
3709 LevelStats_setPlayed(i, 0);
3710 LevelStats_setSolved(i, 0);
3713 checkSeriesInfo(leveldir_current);
3715 /* ----------------------------------------------------------------------- */
3716 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3717 /* ----------------------------------------------------------------------- */
3719 level_subdir = leveldir_current->subdir;
3721 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3723 if ((level_setup_hash = loadSetupFileHash(filename)))
3727 /* get last played level in this level set */
3729 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3733 level_nr = atoi(token_value);
3735 if (level_nr < leveldir_current->first_level)
3736 level_nr = leveldir_current->first_level;
3737 if (level_nr > leveldir_current->last_level)
3738 level_nr = leveldir_current->last_level;
3741 /* get handicap level in this level set */
3743 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3747 int level_nr = atoi(token_value);
3749 if (level_nr < leveldir_current->first_level)
3750 level_nr = leveldir_current->first_level;
3751 if (level_nr > leveldir_current->last_level + 1)
3752 level_nr = leveldir_current->last_level;
3754 if (leveldir_current->user_defined || !leveldir_current->handicap)
3755 level_nr = leveldir_current->last_level;
3757 leveldir_current->handicap_level = level_nr;
3760 /* get number of played and solved levels in this level set */
3762 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3764 char *token = HASH_ITERATION_TOKEN(itr);
3765 char *value = HASH_ITERATION_VALUE(itr);
3767 if (strlen(token) == 3 &&
3768 token[0] >= '0' && token[0] <= '9' &&
3769 token[1] >= '0' && token[1] <= '9' &&
3770 token[2] >= '0' && token[2] <= '9')
3772 int level_nr = atoi(token);
3775 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3777 value = strchr(value, ' ');
3780 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3783 END_HASH_ITERATION(hash, itr)
3785 checkSetupFileHashIdentifier(level_setup_hash, filename,
3786 getCookie("LEVELSETUP"));
3788 freeSetupFileHash(level_setup_hash);
3791 Error(ERR_WARN, "using default setup values");
3796 void SaveLevelSetup_SeriesInfo()
3799 char *level_subdir = leveldir_current->subdir;
3800 char *level_nr_str = int2str(level_nr, 0);
3801 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3805 /* ----------------------------------------------------------------------- */
3806 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3807 /* ----------------------------------------------------------------------- */
3809 InitLevelSetupDirectory(level_subdir);
3811 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3813 if (!(file = fopen(filename, MODE_WRITE)))
3815 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3820 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3821 getCookie("LEVELSETUP")));
3822 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3824 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3825 handicap_level_str));
3827 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3830 if (LevelStats_getPlayed(i) > 0 ||
3831 LevelStats_getSolved(i) > 0)
3836 sprintf(token, "%03d", i);
3837 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3839 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3845 SetFilePermissions(filename, PERMS_PRIVATE);
3850 int LevelStats_getPlayed(int nr)
3852 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3855 int LevelStats_getSolved(int nr)
3857 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3860 void LevelStats_setPlayed(int nr, int value)
3862 if (nr >= 0 && nr < MAX_LEVELS)
3863 level_stats[nr].played = value;
3866 void LevelStats_setSolved(int nr, int value)
3868 if (nr >= 0 && nr < MAX_LEVELS)
3869 level_stats[nr].solved = value;
3872 void LevelStats_incPlayed(int nr)
3874 if (nr >= 0 && nr < MAX_LEVELS)
3875 level_stats[nr].played++;
3878 void LevelStats_incSolved(int nr)
3880 if (nr >= 0 && nr < MAX_LEVELS)
3881 level_stats[nr].solved++;