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 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2880 pushTreeInfo(node_first, leveldir_new);
2882 freeSetupFileHash(setup_file_hash);
2884 if (leveldir_new->level_group)
2886 /* create node to link back to current level directory */
2887 createParentTreeInfoNode(leveldir_new);
2889 /* recursively step into sub-directory and look for more level series */
2890 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2891 leveldir_new, directory_path);
2894 free(directory_path);
2900 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2901 TreeInfo *node_parent,
2902 char *level_directory)
2905 DirectoryEntry *dir_entry;
2906 boolean valid_entry_found = FALSE;
2908 if ((dir = openDirectory(level_directory)) == NULL)
2910 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2915 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2917 char *directory_name = dir_entry->basename;
2918 char *directory_path = getPath2(level_directory, directory_name);
2920 /* skip entries for current and parent directory */
2921 if (strEqual(directory_name, ".") ||
2922 strEqual(directory_name, ".."))
2924 free(directory_path);
2929 /* find out if directory entry is itself a directory */
2930 if (!dir_entry->is_directory) /* not a directory */
2932 free(directory_path);
2937 free(directory_path);
2939 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2940 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2941 strEqual(directory_name, MUSIC_DIRECTORY))
2944 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2949 closeDirectory(dir);
2951 /* special case: top level directory may directly contain "levelinfo.conf" */
2952 if (node_parent == NULL && !valid_entry_found)
2954 /* check if this directory directly contains a file "levelinfo.conf" */
2955 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2956 level_directory, ".");
2959 if (!valid_entry_found)
2960 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2964 boolean AdjustGraphicsForEMC()
2966 boolean settings_changed = FALSE;
2968 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2969 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2971 return settings_changed;
2974 void LoadLevelInfo()
2976 InitUserLevelDirectory(getLoginName());
2978 DrawInitText("Loading level series", 120, FC_GREEN);
2980 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2981 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2983 /* after loading all level set information, clone the level directory tree
2984 and remove all level sets without levels (these may still contain artwork
2985 to be offered in the setup menu as "custom artwork", and are therefore
2986 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2987 leveldir_first_all = leveldir_first;
2988 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2990 AdjustGraphicsForEMC();
2992 /* before sorting, the first entries will be from the user directory */
2993 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2995 if (leveldir_first == NULL)
2996 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2998 sortTreeInfo(&leveldir_first);
3000 #if ENABLE_UNUSED_CODE
3001 dumpTreeInfo(leveldir_first, 0);
3005 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3006 TreeInfo *node_parent,
3007 char *base_directory,
3008 char *directory_name, int type)
3010 char *directory_path = getPath2(base_directory, directory_name);
3011 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3012 SetupFileHash *setup_file_hash = NULL;
3013 TreeInfo *artwork_new = NULL;
3016 if (fileExists(filename))
3017 setup_file_hash = loadSetupFileHash(filename);
3019 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3022 DirectoryEntry *dir_entry;
3023 boolean valid_file_found = FALSE;
3025 if ((dir = openDirectory(directory_path)) != NULL)
3027 while ((dir_entry = readDirectory(dir)) != NULL)
3029 if (FileIsArtworkType(dir_entry->filename, type))
3031 valid_file_found = TRUE;
3037 closeDirectory(dir);
3040 if (!valid_file_found)
3042 if (!strEqual(directory_name, "."))
3043 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3045 free(directory_path);
3052 artwork_new = newTreeInfo();
3055 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3057 setTreeInfoToDefaults(artwork_new, type);
3059 artwork_new->subdir = getStringCopy(directory_name);
3061 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3063 /* set all structure fields according to the token/value pairs */
3065 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3066 setSetupInfo(levelinfo_tokens, i,
3067 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3070 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3071 setString(&artwork_new->name, artwork_new->subdir);
3073 if (artwork_new->identifier == NULL)
3074 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3076 if (artwork_new->name_sorting == NULL)
3077 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3080 if (node_parent == NULL) /* top level group */
3082 artwork_new->basepath = getStringCopy(base_directory);
3083 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3085 else /* sub level group */
3087 artwork_new->basepath = getStringCopy(node_parent->basepath);
3088 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3091 artwork_new->in_user_dir =
3092 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3094 /* (may use ".sort_priority" from "setup_file_hash" above) */
3095 artwork_new->color = ARTWORKCOLOR(artwork_new);
3097 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3099 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3101 if (strEqual(artwork_new->subdir, "."))
3103 if (artwork_new->user_defined)
3105 setString(&artwork_new->identifier, "private");
3106 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3110 setString(&artwork_new->identifier, "classic");
3111 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3114 /* set to new values after changing ".sort_priority" */
3115 artwork_new->color = ARTWORKCOLOR(artwork_new);
3117 setString(&artwork_new->class_desc,
3118 getLevelClassDescription(artwork_new));
3122 setString(&artwork_new->identifier, artwork_new->subdir);
3125 setString(&artwork_new->name, artwork_new->identifier);
3126 setString(&artwork_new->name_sorting, artwork_new->name);
3129 pushTreeInfo(node_first, artwork_new);
3131 freeSetupFileHash(setup_file_hash);
3133 free(directory_path);
3139 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3140 TreeInfo *node_parent,
3141 char *base_directory, int type)
3144 DirectoryEntry *dir_entry;
3145 boolean valid_entry_found = FALSE;
3147 if ((dir = openDirectory(base_directory)) == NULL)
3149 /* display error if directory is main "options.graphics_directory" etc. */
3150 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3151 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3156 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3158 char *directory_name = dir_entry->basename;
3159 char *directory_path = getPath2(base_directory, directory_name);
3161 /* skip directory entries for current and parent directory */
3162 if (strEqual(directory_name, ".") ||
3163 strEqual(directory_name, ".."))
3165 free(directory_path);
3170 /* skip directory entries which are not a directory */
3171 if (!dir_entry->is_directory) /* not a directory */
3173 free(directory_path);
3178 free(directory_path);
3180 /* check if this directory contains artwork with or without config file */
3181 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3183 directory_name, type);
3186 closeDirectory(dir);
3188 /* check if this directory directly contains artwork itself */
3189 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3190 base_directory, ".",
3192 if (!valid_entry_found)
3193 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3197 static TreeInfo *getDummyArtworkInfo(int type)
3199 /* this is only needed when there is completely no artwork available */
3200 TreeInfo *artwork_new = newTreeInfo();
3202 setTreeInfoToDefaults(artwork_new, type);
3204 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3205 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3206 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3208 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3209 setString(&artwork_new->name, UNDEFINED_FILENAME);
3210 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3215 void LoadArtworkInfo()
3217 LoadArtworkInfoCache();
3219 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3221 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3222 options.graphics_directory,
3223 TREE_TYPE_GRAPHICS_DIR);
3224 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3225 getUserGraphicsDir(),
3226 TREE_TYPE_GRAPHICS_DIR);
3228 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3229 options.sounds_directory,
3230 TREE_TYPE_SOUNDS_DIR);
3231 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3233 TREE_TYPE_SOUNDS_DIR);
3235 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3236 options.music_directory,
3237 TREE_TYPE_MUSIC_DIR);
3238 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3240 TREE_TYPE_MUSIC_DIR);
3242 if (artwork.gfx_first == NULL)
3243 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3244 if (artwork.snd_first == NULL)
3245 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3246 if (artwork.mus_first == NULL)
3247 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3249 /* before sorting, the first entries will be from the user directory */
3250 artwork.gfx_current =
3251 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3252 if (artwork.gfx_current == NULL)
3253 artwork.gfx_current =
3254 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3255 if (artwork.gfx_current == NULL)
3256 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3258 artwork.snd_current =
3259 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3260 if (artwork.snd_current == NULL)
3261 artwork.snd_current =
3262 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3263 if (artwork.snd_current == NULL)
3264 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3266 artwork.mus_current =
3267 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3268 if (artwork.mus_current == NULL)
3269 artwork.mus_current =
3270 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3271 if (artwork.mus_current == NULL)
3272 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3274 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3275 artwork.snd_current_identifier = artwork.snd_current->identifier;
3276 artwork.mus_current_identifier = artwork.mus_current->identifier;
3278 #if ENABLE_UNUSED_CODE
3279 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3280 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3281 printf("music set == %s\n\n", artwork.mus_current_identifier);
3284 sortTreeInfo(&artwork.gfx_first);
3285 sortTreeInfo(&artwork.snd_first);
3286 sortTreeInfo(&artwork.mus_first);
3288 #if ENABLE_UNUSED_CODE
3289 dumpTreeInfo(artwork.gfx_first, 0);
3290 dumpTreeInfo(artwork.snd_first, 0);
3291 dumpTreeInfo(artwork.mus_first, 0);
3295 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3296 LevelDirTree *level_node)
3298 int type = (*artwork_node)->type;
3300 /* recursively check all level directories for artwork sub-directories */
3304 /* check all tree entries for artwork, but skip parent link entries */
3305 if (!level_node->parent_link)
3307 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3308 boolean cached = (artwork_new != NULL);
3312 pushTreeInfo(artwork_node, artwork_new);
3316 TreeInfo *topnode_last = *artwork_node;
3317 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3318 ARTWORK_DIRECTORY(type));
3320 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3322 if (topnode_last != *artwork_node) /* check for newly added node */
3324 artwork_new = *artwork_node;
3326 setString(&artwork_new->identifier, level_node->subdir);
3327 setString(&artwork_new->name, level_node->name);
3328 setString(&artwork_new->name_sorting, level_node->name_sorting);
3330 artwork_new->sort_priority = level_node->sort_priority;
3331 artwork_new->color = LEVELCOLOR(artwork_new);
3337 /* insert artwork info (from old cache or filesystem) into new cache */
3338 if (artwork_new != NULL)
3339 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3342 DrawInitText(level_node->name, 150, FC_YELLOW);
3344 if (level_node->node_group != NULL)
3345 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3347 level_node = level_node->next;
3351 void LoadLevelArtworkInfo()
3353 print_timestamp_init("LoadLevelArtworkInfo");
3355 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3357 print_timestamp_time("DrawTimeText");
3359 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3360 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3361 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3362 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3363 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3364 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3366 SaveArtworkInfoCache();
3368 print_timestamp_time("SaveArtworkInfoCache");
3370 /* needed for reloading level artwork not known at ealier stage */
3372 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3374 artwork.gfx_current =
3375 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3376 if (artwork.gfx_current == NULL)
3377 artwork.gfx_current =
3378 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3379 if (artwork.gfx_current == NULL)
3380 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3383 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3385 artwork.snd_current =
3386 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3387 if (artwork.snd_current == NULL)
3388 artwork.snd_current =
3389 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3390 if (artwork.snd_current == NULL)
3391 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3394 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3396 artwork.mus_current =
3397 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3398 if (artwork.mus_current == NULL)
3399 artwork.mus_current =
3400 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3401 if (artwork.mus_current == NULL)
3402 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3405 print_timestamp_time("getTreeInfoFromIdentifier");
3407 sortTreeInfo(&artwork.gfx_first);
3408 sortTreeInfo(&artwork.snd_first);
3409 sortTreeInfo(&artwork.mus_first);
3411 print_timestamp_time("sortTreeInfo");
3413 #if ENABLE_UNUSED_CODE
3414 dumpTreeInfo(artwork.gfx_first, 0);
3415 dumpTreeInfo(artwork.snd_first, 0);
3416 dumpTreeInfo(artwork.mus_first, 0);
3419 print_timestamp_done("LoadLevelArtworkInfo");
3422 static void SaveUserLevelInfo()
3424 LevelDirTree *level_info;
3429 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3431 if (!(file = fopen(filename, MODE_WRITE)))
3433 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3438 level_info = newTreeInfo();
3440 /* always start with reliable default values */
3441 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3443 setString(&level_info->name, getLoginName());
3444 setString(&level_info->author, getRealName());
3445 level_info->levels = 100;
3446 level_info->first_level = 1;
3448 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3450 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3451 getCookie("LEVELINFO")));
3454 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3456 if (i == LEVELINFO_TOKEN_NAME ||
3457 i == LEVELINFO_TOKEN_AUTHOR ||
3458 i == LEVELINFO_TOKEN_LEVELS ||
3459 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3460 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3462 /* just to make things nicer :) */
3463 if (i == LEVELINFO_TOKEN_AUTHOR)
3464 fprintf(file, "\n");
3467 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3471 SetFilePermissions(filename, PERMS_PRIVATE);
3473 freeTreeInfo(level_info);
3477 char *getSetupValue(int type, void *value)
3479 static char value_string[MAX_LINE_LEN];
3487 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3491 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3495 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3496 *(int *)value == FALSE ? "off" : "on"));
3500 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3503 case TYPE_YES_NO_AUTO:
3504 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3505 *(int *)value == FALSE ? "no" : "yes"));
3509 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3513 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3517 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3521 sprintf(value_string, "%d", *(int *)value);
3525 if (*(char **)value == NULL)
3528 strcpy(value_string, *(char **)value);
3532 value_string[0] = '\0';
3536 if (type & TYPE_GHOSTED)
3537 strcpy(value_string, "n/a");
3539 return value_string;
3542 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3546 static char token_string[MAX_LINE_LEN];
3547 int token_type = token_info[token_nr].type;
3548 void *setup_value = token_info[token_nr].value;
3549 char *token_text = token_info[token_nr].text;
3550 char *value_string = getSetupValue(token_type, setup_value);
3552 /* build complete token string */
3553 sprintf(token_string, "%s%s", prefix, token_text);
3555 /* build setup entry line */
3556 line = getFormattedSetupEntry(token_string, value_string);
3558 if (token_type == TYPE_KEY_X11)
3560 Key key = *(Key *)setup_value;
3561 char *keyname = getKeyNameFromKey(key);
3563 /* add comment, if useful */
3564 if (!strEqual(keyname, "(undefined)") &&
3565 !strEqual(keyname, "(unknown)"))
3567 /* add at least one whitespace */
3569 for (i = strlen(line); i < token_comment_position; i++)
3573 strcat(line, keyname);
3580 void LoadLevelSetup_LastSeries()
3582 /* ----------------------------------------------------------------------- */
3583 /* ~/.<program>/levelsetup.conf */
3584 /* ----------------------------------------------------------------------- */
3586 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3587 SetupFileHash *level_setup_hash = NULL;
3589 /* always start with reliable default values */
3590 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3592 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3593 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3595 if (leveldir_current == NULL)
3596 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3599 if ((level_setup_hash = loadSetupFileHash(filename)))
3601 char *last_level_series =
3602 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3604 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3606 if (leveldir_current == NULL)
3607 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3609 checkSetupFileHashIdentifier(level_setup_hash, filename,
3610 getCookie("LEVELSETUP"));
3612 freeSetupFileHash(level_setup_hash);
3615 Error(ERR_WARN, "using default setup values");
3620 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3622 /* ----------------------------------------------------------------------- */
3623 /* ~/.<program>/levelsetup.conf */
3624 /* ----------------------------------------------------------------------- */
3626 // check if the current level directory structure is available at this point
3627 if (leveldir_current == NULL)
3630 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3631 char *level_subdir = leveldir_current->subdir;
3634 InitUserDataDirectory();
3636 if (!(file = fopen(filename, MODE_WRITE)))
3638 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3645 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3646 getCookie("LEVELSETUP")));
3648 if (deactivate_last_level_series)
3649 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3651 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3656 SetFilePermissions(filename, PERMS_PRIVATE);
3661 void SaveLevelSetup_LastSeries()
3663 SaveLevelSetup_LastSeries_Ext(FALSE);
3666 void SaveLevelSetup_LastSeries_Deactivate()
3668 SaveLevelSetup_LastSeries_Ext(TRUE);
3671 static void checkSeriesInfo()
3673 static char *level_directory = NULL;
3676 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3678 level_directory = getPath2((leveldir_current->in_user_dir ?
3679 getUserLevelDir(NULL) :
3680 options.level_directory),
3681 leveldir_current->fullpath);
3683 if ((dir = openDirectory(level_directory)) == NULL)
3685 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3690 closeDirectory(dir);
3693 void LoadLevelSetup_SeriesInfo()
3696 SetupFileHash *level_setup_hash = NULL;
3697 char *level_subdir = leveldir_current->subdir;
3700 /* always start with reliable default values */
3701 level_nr = leveldir_current->first_level;
3703 for (i = 0; i < MAX_LEVELS; i++)
3705 LevelStats_setPlayed(i, 0);
3706 LevelStats_setSolved(i, 0);
3709 checkSeriesInfo(leveldir_current);
3711 /* ----------------------------------------------------------------------- */
3712 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3713 /* ----------------------------------------------------------------------- */
3715 level_subdir = leveldir_current->subdir;
3717 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3719 if ((level_setup_hash = loadSetupFileHash(filename)))
3723 /* get last played level in this level set */
3725 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3729 level_nr = atoi(token_value);
3731 if (level_nr < leveldir_current->first_level)
3732 level_nr = leveldir_current->first_level;
3733 if (level_nr > leveldir_current->last_level)
3734 level_nr = leveldir_current->last_level;
3737 /* get handicap level in this level set */
3739 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3743 int level_nr = atoi(token_value);
3745 if (level_nr < leveldir_current->first_level)
3746 level_nr = leveldir_current->first_level;
3747 if (level_nr > leveldir_current->last_level + 1)
3748 level_nr = leveldir_current->last_level;
3750 if (leveldir_current->user_defined || !leveldir_current->handicap)
3751 level_nr = leveldir_current->last_level;
3753 leveldir_current->handicap_level = level_nr;
3756 /* get number of played and solved levels in this level set */
3758 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3760 char *token = HASH_ITERATION_TOKEN(itr);
3761 char *value = HASH_ITERATION_VALUE(itr);
3763 if (strlen(token) == 3 &&
3764 token[0] >= '0' && token[0] <= '9' &&
3765 token[1] >= '0' && token[1] <= '9' &&
3766 token[2] >= '0' && token[2] <= '9')
3768 int level_nr = atoi(token);
3771 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3773 value = strchr(value, ' ');
3776 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3779 END_HASH_ITERATION(hash, itr)
3781 checkSetupFileHashIdentifier(level_setup_hash, filename,
3782 getCookie("LEVELSETUP"));
3784 freeSetupFileHash(level_setup_hash);
3787 Error(ERR_WARN, "using default setup values");
3792 void SaveLevelSetup_SeriesInfo()
3795 char *level_subdir = leveldir_current->subdir;
3796 char *level_nr_str = int2str(level_nr, 0);
3797 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3801 /* ----------------------------------------------------------------------- */
3802 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3803 /* ----------------------------------------------------------------------- */
3805 InitLevelSetupDirectory(level_subdir);
3807 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3809 if (!(file = fopen(filename, MODE_WRITE)))
3811 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3816 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3817 getCookie("LEVELSETUP")));
3818 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3820 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3821 handicap_level_str));
3823 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3826 if (LevelStats_getPlayed(i) > 0 ||
3827 LevelStats_getSolved(i) > 0)
3832 sprintf(token, "%03d", i);
3833 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3835 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3841 SetFilePermissions(filename, PERMS_PRIVATE);
3846 int LevelStats_getPlayed(int nr)
3848 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3851 int LevelStats_getSolved(int nr)
3853 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3856 void LevelStats_setPlayed(int nr, int value)
3858 if (nr >= 0 && nr < MAX_LEVELS)
3859 level_stats[nr].played = value;
3862 void LevelStats_setSolved(int nr, int value)
3864 if (nr >= 0 && nr < MAX_LEVELS)
3865 level_stats[nr].solved = value;
3868 void LevelStats_incPlayed(int nr)
3870 if (nr >= 0 && nr < MAX_LEVELS)
3871 level_stats[nr].played++;
3874 void LevelStats_incSolved(int nr)
3876 if (nr >= 0 && nr < MAX_LEVELS)
3877 level_stats[nr].solved++;