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;
321 checked_free(artwork_dir);
323 artwork_dir = getPath2(ti->basepath, ti->fullpath);
328 char *setLevelArtworkDir(TreeInfo *ti)
330 char **artwork_path_ptr, **artwork_set_ptr;
331 TreeInfo *level_artwork;
333 if (ti == NULL || leveldir_current == NULL)
336 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
337 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
339 checked_free(*artwork_path_ptr);
341 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
343 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
348 No (or non-existing) artwork configured in "levelinfo.conf". This would
349 normally result in using the artwork configured in the setup menu. But
350 if an artwork subdirectory exists (which might contain custom artwork
351 or an artwork configuration file), this level artwork must be treated
352 as relative to the default "classic" artwork, not to the artwork that
353 is currently configured in the setup menu.
355 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
356 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
357 the real "classic" artwork from the original R'n'D (like "gfx_classic").
360 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
362 checked_free(*artwork_set_ptr);
364 if (directoryExists(dir))
366 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
367 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
371 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
372 *artwork_set_ptr = NULL;
378 return *artwork_set_ptr;
381 inline static char *getLevelArtworkSet(int type)
383 if (leveldir_current == NULL)
386 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
389 inline static char *getLevelArtworkDir(int type)
391 if (leveldir_current == NULL)
392 return UNDEFINED_FILENAME;
394 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
397 char *getTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
410 char *getSolutionTapeFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
418 filename = getPath2(getSolutionTapeDir(), basename);
420 if (!fileExists(filename))
422 static char *filename_sln = NULL;
424 checked_free(filename_sln);
426 sprintf(basename, "%03d.sln", nr);
427 filename_sln = getPath2(getSolutionTapeDir(), basename);
429 if (fileExists(filename_sln))
436 char *getScoreFilename(int nr)
438 static char *filename = NULL;
439 char basename[MAX_FILENAME_LEN];
441 checked_free(filename);
443 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
444 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
449 char *getSetupFilename()
451 static char *filename = NULL;
453 checked_free(filename);
455 filename = getPath2(getSetupDir(), SETUP_FILENAME);
460 char *getEditorSetupFilename()
462 static char *filename = NULL;
464 checked_free(filename);
465 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
467 if (fileExists(filename))
470 checked_free(filename);
471 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
476 char *getHelpAnimFilename()
478 static char *filename = NULL;
480 checked_free(filename);
482 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
487 char *getHelpTextFilename()
489 static char *filename = NULL;
491 checked_free(filename);
493 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
498 char *getLevelSetInfoFilename()
500 static char *filename = NULL;
515 for (i = 0; basenames[i] != NULL; i++)
517 checked_free(filename);
518 filename = getPath2(getCurrentLevelDir(), basenames[i]);
520 if (fileExists(filename))
527 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
529 static char basename[32];
531 sprintf(basename, "%s_%d.txt",
532 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
537 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
539 static char *filename = NULL;
541 boolean skip_setup_artwork = FALSE;
543 checked_free(filename);
545 basename = getLevelSetTitleMessageBasename(nr, initial);
547 if (!gfx.override_level_graphics)
549 /* 1st try: look for special artwork in current level series directory */
550 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
551 if (fileExists(filename))
556 /* 2nd try: look for message file in current level set directory */
557 filename = getPath2(getCurrentLevelDir(), basename);
558 if (fileExists(filename))
563 /* check if there is special artwork configured in level series config */
564 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
566 /* 3rd try: look for special artwork configured in level series config */
567 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
568 if (fileExists(filename))
573 /* take missing artwork configured in level set config from default */
574 skip_setup_artwork = TRUE;
578 if (!skip_setup_artwork)
580 /* 4th try: look for special artwork in configured artwork directory */
581 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
582 if (fileExists(filename))
588 /* 5th try: look for default artwork in new default artwork directory */
589 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
590 if (fileExists(filename))
595 /* 6th try: look for default artwork in old default artwork directory */
596 filename = getPath2(options.graphics_directory, basename);
597 if (fileExists(filename))
600 return NULL; /* cannot find specified artwork file anywhere */
603 static char *getCorrectedArtworkBasename(char *basename)
608 char *getCustomImageFilename(char *basename)
610 static char *filename = NULL;
611 boolean skip_setup_artwork = FALSE;
613 checked_free(filename);
615 basename = getCorrectedArtworkBasename(basename);
617 if (!gfx.override_level_graphics)
619 /* 1st try: look for special artwork in current level series directory */
620 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
621 if (fileExists(filename))
626 /* check if there is special artwork configured in level series config */
627 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
629 /* 2nd try: look for special artwork configured in level series config */
630 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
631 if (fileExists(filename))
636 /* take missing artwork configured in level set config from default */
637 skip_setup_artwork = TRUE;
641 if (!skip_setup_artwork)
643 /* 3rd try: look for special artwork in configured artwork directory */
644 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
645 if (fileExists(filename))
651 /* 4th try: look for default artwork in new default artwork directory */
652 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
653 if (fileExists(filename))
658 /* 5th try: look for default artwork in old default artwork directory */
659 filename = getImg2(options.graphics_directory, basename);
660 if (fileExists(filename))
663 #if defined(CREATE_SPECIAL_EDITION)
667 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
669 /* 6th try: look for fallback artwork in old default artwork directory */
670 /* (needed to prevent errors when trying to access unused artwork files) */
671 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
672 if (fileExists(filename))
676 return NULL; /* cannot find specified artwork file anywhere */
679 char *getCustomSoundFilename(char *basename)
681 static char *filename = NULL;
682 boolean skip_setup_artwork = FALSE;
684 checked_free(filename);
686 basename = getCorrectedArtworkBasename(basename);
688 if (!gfx.override_level_sounds)
690 /* 1st try: look for special artwork in current level series directory */
691 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
692 if (fileExists(filename))
697 /* check if there is special artwork configured in level series config */
698 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
700 /* 2nd try: look for special artwork configured in level series config */
701 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
702 if (fileExists(filename))
707 /* take missing artwork configured in level set config from default */
708 skip_setup_artwork = TRUE;
712 if (!skip_setup_artwork)
714 /* 3rd try: look for special artwork in configured artwork directory */
715 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
716 if (fileExists(filename))
722 /* 4th try: look for default artwork in new default artwork directory */
723 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
724 if (fileExists(filename))
729 /* 5th try: look for default artwork in old default artwork directory */
730 filename = getPath2(options.sounds_directory, basename);
731 if (fileExists(filename))
734 #if defined(CREATE_SPECIAL_EDITION)
738 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
740 /* 6th try: look for fallback artwork in old default artwork directory */
741 /* (needed to prevent errors when trying to access unused artwork files) */
742 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
743 if (fileExists(filename))
747 return NULL; /* cannot find specified artwork file anywhere */
750 char *getCustomMusicFilename(char *basename)
752 static char *filename = NULL;
753 boolean skip_setup_artwork = FALSE;
755 checked_free(filename);
757 basename = getCorrectedArtworkBasename(basename);
759 if (!gfx.override_level_music)
761 /* 1st try: look for special artwork in current level series directory */
762 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
763 if (fileExists(filename))
768 /* check if there is special artwork configured in level series config */
769 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
771 /* 2nd try: look for special artwork configured in level series config */
772 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
773 if (fileExists(filename))
778 /* take missing artwork configured in level set config from default */
779 skip_setup_artwork = TRUE;
783 if (!skip_setup_artwork)
785 /* 3rd try: look for special artwork in configured artwork directory */
786 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
787 if (fileExists(filename))
793 /* 4th try: look for default artwork in new default artwork directory */
794 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
795 if (fileExists(filename))
800 /* 5th try: look for default artwork in old default artwork directory */
801 filename = getPath2(options.music_directory, basename);
802 if (fileExists(filename))
805 #if defined(CREATE_SPECIAL_EDITION)
809 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
811 /* 6th try: look for fallback artwork in old default artwork directory */
812 /* (needed to prevent errors when trying to access unused artwork files) */
813 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
814 if (fileExists(filename))
818 return NULL; /* cannot find specified artwork file anywhere */
821 char *getCustomArtworkFilename(char *basename, int type)
823 if (type == ARTWORK_TYPE_GRAPHICS)
824 return getCustomImageFilename(basename);
825 else if (type == ARTWORK_TYPE_SOUNDS)
826 return getCustomSoundFilename(basename);
827 else if (type == ARTWORK_TYPE_MUSIC)
828 return getCustomMusicFilename(basename);
830 return UNDEFINED_FILENAME;
833 char *getCustomArtworkConfigFilename(int type)
835 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
838 char *getCustomArtworkLevelConfigFilename(int type)
840 static char *filename = NULL;
842 checked_free(filename);
844 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
849 char *getCustomMusicDirectory(void)
851 static char *directory = NULL;
852 boolean skip_setup_artwork = FALSE;
854 checked_free(directory);
856 if (!gfx.override_level_music)
858 /* 1st try: look for special artwork in current level series directory */
859 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
860 if (directoryExists(directory))
865 /* check if there is special artwork configured in level series config */
866 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
868 /* 2nd try: look for special artwork configured in level series config */
869 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
870 if (directoryExists(directory))
875 /* take missing artwork configured in level set config from default */
876 skip_setup_artwork = TRUE;
880 if (!skip_setup_artwork)
882 /* 3rd try: look for special artwork in configured artwork directory */
883 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
884 if (directoryExists(directory))
890 /* 4th try: look for default artwork in new default artwork directory */
891 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
892 if (directoryExists(directory))
897 /* 5th try: look for default artwork in old default artwork directory */
898 directory = getStringCopy(options.music_directory);
899 if (directoryExists(directory))
902 return NULL; /* cannot find specified artwork file anywhere */
905 void InitTapeDirectory(char *level_subdir)
907 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
908 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
909 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
912 void InitScoreDirectory(char *level_subdir)
914 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
915 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
916 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
919 static void SaveUserLevelInfo();
921 void InitUserLevelDirectory(char *level_subdir)
923 if (!directoryExists(getUserLevelDir(level_subdir)))
925 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
926 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
927 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
933 void InitLevelSetupDirectory(char *level_subdir)
935 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
936 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
937 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
940 void InitCacheDirectory()
942 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
943 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
947 /* ------------------------------------------------------------------------- */
948 /* some functions to handle lists of level and artwork directories */
949 /* ------------------------------------------------------------------------- */
951 TreeInfo *newTreeInfo()
953 return checked_calloc(sizeof(TreeInfo));
956 TreeInfo *newTreeInfo_setDefaults(int type)
958 TreeInfo *ti = newTreeInfo();
960 setTreeInfoToDefaults(ti, type);
965 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
967 node_new->next = *node_first;
968 *node_first = node_new;
971 int numTreeInfo(TreeInfo *node)
984 boolean validLevelSeries(TreeInfo *node)
986 return (node != NULL && !node->node_group && !node->parent_link);
989 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
994 if (node->node_group) /* enter level group (step down into tree) */
995 return getFirstValidTreeInfoEntry(node->node_group);
996 else if (node->parent_link) /* skip start entry of level group */
998 if (node->next) /* get first real level series entry */
999 return getFirstValidTreeInfoEntry(node->next);
1000 else /* leave empty level group and go on */
1001 return getFirstValidTreeInfoEntry(node->node_parent->next);
1003 else /* this seems to be a regular level series */
1007 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1012 if (node->node_parent == NULL) /* top level group */
1013 return *node->node_top;
1014 else /* sub level group */
1015 return node->node_parent->node_group;
1018 int numTreeInfoInGroup(TreeInfo *node)
1020 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1023 int posTreeInfo(TreeInfo *node)
1025 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1030 if (node_cmp == node)
1034 node_cmp = node_cmp->next;
1040 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1042 TreeInfo *node_default = node;
1054 return node_default;
1057 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1059 if (identifier == NULL)
1064 if (node->node_group)
1066 TreeInfo *node_group;
1068 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1073 else if (!node->parent_link)
1075 if (strEqual(identifier, node->identifier))
1085 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1086 TreeInfo *node, boolean skip_sets_without_levels)
1093 if (!node->parent_link && !node->level_group &&
1094 skip_sets_without_levels && node->levels == 0)
1095 return cloneTreeNode(node_top, node_parent, node->next,
1096 skip_sets_without_levels);
1098 node_new = getTreeInfoCopy(node); /* copy complete node */
1100 node_new->node_top = node_top; /* correct top node link */
1101 node_new->node_parent = node_parent; /* correct parent node link */
1103 if (node->level_group)
1104 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1105 skip_sets_without_levels);
1107 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1108 skip_sets_without_levels);
1113 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1115 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1117 *ti_new = ti_cloned;
1120 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1122 boolean settings_changed = FALSE;
1126 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1127 !strEqual(node->graphics_set, node->graphics_set_ecs))
1129 setString(&node->graphics_set, node->graphics_set_ecs);
1130 settings_changed = TRUE;
1132 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1133 !strEqual(node->graphics_set, node->graphics_set_aga))
1135 setString(&node->graphics_set, node->graphics_set_aga);
1136 settings_changed = TRUE;
1139 if (node->node_group != NULL)
1140 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1145 return settings_changed;
1148 void dumpTreeInfo(TreeInfo *node, int depth)
1152 printf("Dumping TreeInfo:\n");
1156 for (i = 0; i < (depth + 1) * 3; i++)
1159 printf("'%s' / '%s'\n", node->identifier, node->name);
1162 // use for dumping artwork info tree
1163 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1164 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1167 if (node->node_group != NULL)
1168 dumpTreeInfo(node->node_group, depth + 1);
1174 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1175 int (*compare_function)(const void *,
1178 int num_nodes = numTreeInfo(*node_first);
1179 TreeInfo **sort_array;
1180 TreeInfo *node = *node_first;
1186 /* allocate array for sorting structure pointers */
1187 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1189 /* writing structure pointers to sorting array */
1190 while (i < num_nodes && node) /* double boundary check... */
1192 sort_array[i] = node;
1198 /* sorting the structure pointers in the sorting array */
1199 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1202 /* update the linkage of list elements with the sorted node array */
1203 for (i = 0; i < num_nodes - 1; i++)
1204 sort_array[i]->next = sort_array[i + 1];
1205 sort_array[num_nodes - 1]->next = NULL;
1207 /* update the linkage of the main list anchor pointer */
1208 *node_first = sort_array[0];
1212 /* now recursively sort the level group structures */
1216 if (node->node_group != NULL)
1217 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1223 void sortTreeInfo(TreeInfo **node_first)
1225 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1229 /* ========================================================================= */
1230 /* some stuff from "files.c" */
1231 /* ========================================================================= */
1233 #if defined(PLATFORM_WIN32)
1235 #define S_IRGRP S_IRUSR
1238 #define S_IROTH S_IRUSR
1241 #define S_IWGRP S_IWUSR
1244 #define S_IWOTH S_IWUSR
1247 #define S_IXGRP S_IXUSR
1250 #define S_IXOTH S_IXUSR
1253 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1258 #endif /* PLATFORM_WIN32 */
1260 /* file permissions for newly written files */
1261 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1262 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1263 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1265 #define MODE_W_PRIVATE (S_IWUSR)
1266 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1267 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1269 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1270 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1272 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1273 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1277 static char *dir = NULL;
1279 #if defined(PLATFORM_WIN32)
1282 dir = checked_malloc(MAX_PATH + 1);
1284 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1287 #elif defined(PLATFORM_UNIX)
1290 if ((dir = getenv("HOME")) == NULL)
1294 if ((pwd = getpwuid(getuid())) != NULL)
1295 dir = getStringCopy(pwd->pw_dir);
1307 char *getCommonDataDir(void)
1309 static char *common_data_dir = NULL;
1311 #if defined(PLATFORM_WIN32)
1312 if (common_data_dir == NULL)
1314 char *dir = checked_malloc(MAX_PATH + 1);
1316 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1317 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1318 common_data_dir = getPath2(dir, program.userdata_subdir);
1320 common_data_dir = options.rw_base_directory;
1323 if (common_data_dir == NULL)
1324 common_data_dir = options.rw_base_directory;
1327 return common_data_dir;
1330 char *getPersonalDataDir(void)
1332 static char *personal_data_dir = NULL;
1334 #if defined(PLATFORM_MACOSX)
1335 if (personal_data_dir == NULL)
1336 personal_data_dir = getPath2(getHomeDir(), "Documents");
1338 if (personal_data_dir == NULL)
1339 personal_data_dir = getHomeDir();
1342 return personal_data_dir;
1345 char *getUserGameDataDir(void)
1347 static char *user_game_data_dir = NULL;
1349 #if defined(PLATFORM_ANDROID)
1350 if (user_game_data_dir == NULL)
1351 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1353 if (user_game_data_dir == NULL)
1354 user_game_data_dir = getPath2(getPersonalDataDir(),
1355 program.userdata_subdir);
1358 return user_game_data_dir;
1361 void updateUserGameDataDir()
1363 #if defined(PLATFORM_MACOSX)
1364 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1365 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1367 /* convert old Unix style game data directory to Mac OS X style, if needed */
1368 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1370 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1372 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1373 userdata_dir_old, userdata_dir_new);
1375 /* continue using Unix style data directory -- this should not happen */
1376 program.userdata_path = getPath2(getPersonalDataDir(),
1377 program.userdata_subdir_unix);
1381 free(userdata_dir_old);
1387 return getUserGameDataDir();
1390 static mode_t posix_umask(mode_t mask)
1392 #if defined(PLATFORM_UNIX)
1399 static int posix_mkdir(const char *pathname, mode_t mode)
1401 #if defined(PLATFORM_WIN32)
1402 return mkdir(pathname);
1404 return mkdir(pathname, mode);
1408 static boolean posix_process_running_setgid()
1410 #if defined(PLATFORM_UNIX)
1411 return (getgid() != getegid());
1417 void createDirectory(char *dir, char *text, int permission_class)
1419 /* leave "other" permissions in umask untouched, but ensure group parts
1420 of USERDATA_DIR_MODE are not masked */
1421 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1422 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1423 mode_t last_umask = posix_umask(0);
1424 mode_t group_umask = ~(dir_mode & S_IRWXG);
1425 int running_setgid = posix_process_running_setgid();
1427 /* if we're setgid, protect files against "other" */
1428 /* else keep umask(0) to make the dir world-writable */
1431 posix_umask(last_umask & group_umask);
1433 dir_mode |= MODE_W_ALL;
1435 if (!directoryExists(dir))
1436 if (posix_mkdir(dir, dir_mode) != 0)
1437 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1438 text, dir, strerror(errno));
1440 if (permission_class == PERMS_PUBLIC && !running_setgid)
1441 chmod(dir, dir_mode);
1443 posix_umask(last_umask); /* restore previous umask */
1446 void InitUserDataDirectory()
1448 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1451 void SetFilePermissions(char *filename, int permission_class)
1453 int running_setgid = posix_process_running_setgid();
1454 int perms = (permission_class == PERMS_PRIVATE ?
1455 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1457 if (permission_class == PERMS_PUBLIC && !running_setgid)
1458 perms |= MODE_W_ALL;
1460 chmod(filename, perms);
1463 char *getCookie(char *file_type)
1465 static char cookie[MAX_COOKIE_LEN + 1];
1467 if (strlen(program.cookie_prefix) + 1 +
1468 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1469 return "[COOKIE ERROR]"; /* should never happen */
1471 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1472 program.cookie_prefix, file_type,
1473 program.version_major, program.version_minor);
1478 int getFileVersionFromCookieString(const char *cookie)
1480 const char *ptr_cookie1, *ptr_cookie2;
1481 const char *pattern1 = "_FILE_VERSION_";
1482 const char *pattern2 = "?.?";
1483 const int len_cookie = strlen(cookie);
1484 const int len_pattern1 = strlen(pattern1);
1485 const int len_pattern2 = strlen(pattern2);
1486 const int len_pattern = len_pattern1 + len_pattern2;
1487 int version_major, version_minor;
1489 if (len_cookie <= len_pattern)
1492 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1493 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1495 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1498 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1499 ptr_cookie2[1] != '.' ||
1500 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1503 version_major = ptr_cookie2[0] - '0';
1504 version_minor = ptr_cookie2[2] - '0';
1506 return VERSION_IDENT(version_major, version_minor, 0, 0);
1509 boolean checkCookieString(const char *cookie, const char *template)
1511 const char *pattern = "_FILE_VERSION_?.?";
1512 const int len_cookie = strlen(cookie);
1513 const int len_template = strlen(template);
1514 const int len_pattern = strlen(pattern);
1516 if (len_cookie != len_template)
1519 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1525 /* ------------------------------------------------------------------------- */
1526 /* setup file list and hash handling functions */
1527 /* ------------------------------------------------------------------------- */
1529 char *getFormattedSetupEntry(char *token, char *value)
1532 static char entry[MAX_LINE_LEN];
1534 /* if value is an empty string, just return token without value */
1538 /* start with the token and some spaces to format output line */
1539 sprintf(entry, "%s:", token);
1540 for (i = strlen(entry); i < token_value_position; i++)
1543 /* continue with the token's value */
1544 strcat(entry, value);
1549 SetupFileList *newSetupFileList(char *token, char *value)
1551 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1553 new->token = getStringCopy(token);
1554 new->value = getStringCopy(value);
1561 void freeSetupFileList(SetupFileList *list)
1566 checked_free(list->token);
1567 checked_free(list->value);
1570 freeSetupFileList(list->next);
1575 char *getListEntry(SetupFileList *list, char *token)
1580 if (strEqual(list->token, token))
1583 return getListEntry(list->next, token);
1586 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1591 if (strEqual(list->token, token))
1593 checked_free(list->value);
1595 list->value = getStringCopy(value);
1599 else if (list->next == NULL)
1600 return (list->next = newSetupFileList(token, value));
1602 return setListEntry(list->next, token, value);
1605 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1610 if (list->next == NULL)
1611 return (list->next = newSetupFileList(token, value));
1613 return addListEntry(list->next, token, value);
1616 #if ENABLE_UNUSED_CODE
1618 static void printSetupFileList(SetupFileList *list)
1623 printf("token: '%s'\n", list->token);
1624 printf("value: '%s'\n", list->value);
1626 printSetupFileList(list->next);
1632 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1633 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1634 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1635 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1637 #define insert_hash_entry hashtable_insert
1638 #define search_hash_entry hashtable_search
1639 #define change_hash_entry hashtable_change
1640 #define remove_hash_entry hashtable_remove
1643 unsigned int get_hash_from_key(void *key)
1648 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1649 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1650 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1651 it works better than many other constants, prime or not) has never been
1652 adequately explained.
1654 If you just want to have a good hash function, and cannot wait, djb2
1655 is one of the best string hash functions i know. It has excellent
1656 distribution and speed on many different sets of keys and table sizes.
1657 You are not likely to do better with one of the "well known" functions
1658 such as PJW, K&R, etc.
1660 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1663 char *str = (char *)key;
1664 unsigned int hash = 5381;
1667 while ((c = *str++))
1668 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1673 static int keys_are_equal(void *key1, void *key2)
1675 return (strEqual((char *)key1, (char *)key2));
1678 SetupFileHash *newSetupFileHash()
1680 SetupFileHash *new_hash =
1681 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1683 if (new_hash == NULL)
1684 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1689 void freeSetupFileHash(SetupFileHash *hash)
1694 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1697 char *getHashEntry(SetupFileHash *hash, char *token)
1702 return search_hash_entry(hash, token);
1705 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1712 value_copy = getStringCopy(value);
1714 /* change value; if it does not exist, insert it as new */
1715 if (!change_hash_entry(hash, token, value_copy))
1716 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1717 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1720 char *removeHashEntry(SetupFileHash *hash, char *token)
1725 return remove_hash_entry(hash, token);
1728 #if ENABLE_UNUSED_CODE
1730 static void printSetupFileHash(SetupFileHash *hash)
1732 BEGIN_HASH_ITERATION(hash, itr)
1734 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1735 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1737 END_HASH_ITERATION(hash, itr)
1742 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1743 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1744 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1746 static boolean token_value_separator_found = FALSE;
1747 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1748 static boolean token_value_separator_warning = FALSE;
1750 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1751 static boolean token_already_exists_warning = FALSE;
1754 static boolean getTokenValueFromSetupLineExt(char *line,
1755 char **token_ptr, char **value_ptr,
1756 char *filename, char *line_raw,
1758 boolean separator_required)
1760 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1761 char *token, *value, *line_ptr;
1763 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1764 if (line_raw == NULL)
1766 strncpy(line_copy, line, MAX_LINE_LEN);
1767 line_copy[MAX_LINE_LEN] = '\0';
1770 strcpy(line_raw_copy, line_copy);
1771 line_raw = line_raw_copy;
1774 /* cut trailing comment from input line */
1775 for (line_ptr = line; *line_ptr; line_ptr++)
1777 if (*line_ptr == '#')
1784 /* cut trailing whitespaces from input line */
1785 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1786 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1789 /* ignore empty lines */
1793 /* cut leading whitespaces from token */
1794 for (token = line; *token; token++)
1795 if (*token != ' ' && *token != '\t')
1798 /* start with empty value as reliable default */
1801 token_value_separator_found = FALSE;
1803 /* find end of token to determine start of value */
1804 for (line_ptr = token; *line_ptr; line_ptr++)
1806 /* first look for an explicit token/value separator, like ':' or '=' */
1807 if (*line_ptr == ':' || *line_ptr == '=')
1809 *line_ptr = '\0'; /* terminate token string */
1810 value = line_ptr + 1; /* set beginning of value */
1812 token_value_separator_found = TRUE;
1818 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1819 /* fallback: if no token/value separator found, also allow whitespaces */
1820 if (!token_value_separator_found && !separator_required)
1822 for (line_ptr = token; *line_ptr; line_ptr++)
1824 if (*line_ptr == ' ' || *line_ptr == '\t')
1826 *line_ptr = '\0'; /* terminate token string */
1827 value = line_ptr + 1; /* set beginning of value */
1829 token_value_separator_found = TRUE;
1835 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1836 if (token_value_separator_found)
1838 if (!token_value_separator_warning)
1840 Error(ERR_INFO_LINE, "-");
1842 if (filename != NULL)
1844 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1845 Error(ERR_INFO, "- config file: '%s'", filename);
1849 Error(ERR_WARN, "missing token/value separator(s):");
1852 token_value_separator_warning = TRUE;
1855 if (filename != NULL)
1856 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1858 Error(ERR_INFO, "- line: '%s'", line_raw);
1864 /* cut trailing whitespaces from token */
1865 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1866 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1869 /* cut leading whitespaces from value */
1870 for (; *value; value++)
1871 if (*value != ' ' && *value != '\t')
1880 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1882 /* while the internal (old) interface does not require a token/value
1883 separator (for downwards compatibility with existing files which
1884 don't use them), it is mandatory for the external (new) interface */
1886 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1889 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1890 boolean top_recursion_level, boolean is_hash)
1892 static SetupFileHash *include_filename_hash = NULL;
1893 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1894 char *token, *value, *line_ptr;
1895 void *insert_ptr = NULL;
1896 boolean read_continued_line = FALSE;
1898 int line_nr = 0, token_count = 0, include_count = 0;
1900 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1901 token_value_separator_warning = FALSE;
1904 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1905 token_already_exists_warning = FALSE;
1908 if (!(file = openFile(filename, MODE_READ)))
1910 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1915 /* use "insert pointer" to store list end for constant insertion complexity */
1917 insert_ptr = setup_file_data;
1919 /* on top invocation, create hash to mark included files (to prevent loops) */
1920 if (top_recursion_level)
1921 include_filename_hash = newSetupFileHash();
1923 /* mark this file as already included (to prevent including it again) */
1924 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1926 while (!checkEndOfFile(file))
1928 /* read next line of input file */
1929 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1932 /* check if line was completely read and is terminated by line break */
1933 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1936 /* cut trailing line break (this can be newline and/or carriage return) */
1937 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1938 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1941 /* copy raw input line for later use (mainly debugging output) */
1942 strcpy(line_raw, line);
1944 if (read_continued_line)
1946 /* append new line to existing line, if there is enough space */
1947 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1948 strcat(previous_line, line_ptr);
1950 strcpy(line, previous_line); /* copy storage buffer to line */
1952 read_continued_line = FALSE;
1955 /* if the last character is '\', continue at next line */
1956 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1958 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1959 strcpy(previous_line, line); /* copy line to storage buffer */
1961 read_continued_line = TRUE;
1966 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1967 line_raw, line_nr, FALSE))
1972 if (strEqual(token, "include"))
1974 if (getHashEntry(include_filename_hash, value) == NULL)
1976 char *basepath = getBasePath(filename);
1977 char *basename = getBaseName(value);
1978 char *filename_include = getPath2(basepath, basename);
1980 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1984 free(filename_include);
1990 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1997 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1999 getHashEntry((SetupFileHash *)setup_file_data, token);
2001 if (old_value != NULL)
2003 if (!token_already_exists_warning)
2005 Error(ERR_INFO_LINE, "-");
2006 Error(ERR_WARN, "duplicate token(s) found in config file:");
2007 Error(ERR_INFO, "- config file: '%s'", filename);
2009 token_already_exists_warning = TRUE;
2012 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2013 Error(ERR_INFO, " old value: '%s'", old_value);
2014 Error(ERR_INFO, " new value: '%s'", value);
2018 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2022 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2032 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2033 if (token_value_separator_warning)
2034 Error(ERR_INFO_LINE, "-");
2037 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2038 if (token_already_exists_warning)
2039 Error(ERR_INFO_LINE, "-");
2042 if (token_count == 0 && include_count == 0)
2043 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2045 if (top_recursion_level)
2046 freeSetupFileHash(include_filename_hash);
2051 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2055 if (!(file = fopen(filename, MODE_WRITE)))
2057 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2062 BEGIN_HASH_ITERATION(hash, itr)
2064 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2065 HASH_ITERATION_VALUE(itr)));
2067 END_HASH_ITERATION(hash, itr)
2072 SetupFileList *loadSetupFileList(char *filename)
2074 SetupFileList *setup_file_list = newSetupFileList("", "");
2075 SetupFileList *first_valid_list_entry;
2077 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2079 freeSetupFileList(setup_file_list);
2084 first_valid_list_entry = setup_file_list->next;
2086 /* free empty list header */
2087 setup_file_list->next = NULL;
2088 freeSetupFileList(setup_file_list);
2090 return first_valid_list_entry;
2093 SetupFileHash *loadSetupFileHash(char *filename)
2095 SetupFileHash *setup_file_hash = newSetupFileHash();
2097 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2099 freeSetupFileHash(setup_file_hash);
2104 return setup_file_hash;
2107 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2108 char *filename, char *identifier)
2110 #if USE_FILE_IDENTIFIERS
2111 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2114 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2115 else if (!checkCookieString(value, identifier))
2116 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2121 /* ========================================================================= */
2122 /* setup file stuff */
2123 /* ========================================================================= */
2125 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2126 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2127 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2129 /* level directory info */
2130 #define LEVELINFO_TOKEN_IDENTIFIER 0
2131 #define LEVELINFO_TOKEN_NAME 1
2132 #define LEVELINFO_TOKEN_NAME_SORTING 2
2133 #define LEVELINFO_TOKEN_AUTHOR 3
2134 #define LEVELINFO_TOKEN_YEAR 4
2135 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2136 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2137 #define LEVELINFO_TOKEN_TESTED_BY 7
2138 #define LEVELINFO_TOKEN_LEVELS 8
2139 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2140 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2141 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2142 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2143 #define LEVELINFO_TOKEN_READONLY 13
2144 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2145 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2146 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2147 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2148 #define LEVELINFO_TOKEN_MUSIC_SET 18
2149 #define LEVELINFO_TOKEN_FILENAME 19
2150 #define LEVELINFO_TOKEN_FILETYPE 20
2151 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2152 #define LEVELINFO_TOKEN_HANDICAP 22
2153 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2155 #define NUM_LEVELINFO_TOKENS 24
2157 static LevelDirTree ldi;
2159 static struct TokenInfo levelinfo_tokens[] =
2161 /* level directory info */
2162 { TYPE_STRING, &ldi.identifier, "identifier" },
2163 { TYPE_STRING, &ldi.name, "name" },
2164 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2165 { TYPE_STRING, &ldi.author, "author" },
2166 { TYPE_STRING, &ldi.year, "year" },
2167 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2168 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2169 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2170 { TYPE_INTEGER, &ldi.levels, "levels" },
2171 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2172 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2173 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2174 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2175 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2176 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2177 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2178 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2179 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2180 { TYPE_STRING, &ldi.music_set, "music_set" },
2181 { TYPE_STRING, &ldi.level_filename, "filename" },
2182 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2183 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2184 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2185 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2188 static struct TokenInfo artworkinfo_tokens[] =
2190 /* artwork directory info */
2191 { TYPE_STRING, &ldi.identifier, "identifier" },
2192 { TYPE_STRING, &ldi.subdir, "subdir" },
2193 { TYPE_STRING, &ldi.name, "name" },
2194 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2195 { TYPE_STRING, &ldi.author, "author" },
2196 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2197 { TYPE_STRING, &ldi.basepath, "basepath" },
2198 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2199 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2200 { TYPE_INTEGER, &ldi.color, "color" },
2201 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2206 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2210 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2211 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2212 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2213 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2216 ti->node_parent = NULL;
2217 ti->node_group = NULL;
2224 ti->fullpath = NULL;
2225 ti->basepath = NULL;
2226 ti->identifier = NULL;
2227 ti->name = getStringCopy(ANONYMOUS_NAME);
2228 ti->name_sorting = NULL;
2229 ti->author = getStringCopy(ANONYMOUS_NAME);
2232 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2233 ti->latest_engine = FALSE; /* default: get from level */
2234 ti->parent_link = FALSE;
2235 ti->in_user_dir = FALSE;
2236 ti->user_defined = FALSE;
2238 ti->class_desc = NULL;
2240 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2242 if (ti->type == TREE_TYPE_LEVEL_DIR)
2244 ti->imported_from = NULL;
2245 ti->imported_by = NULL;
2246 ti->tested_by = NULL;
2248 ti->graphics_set_ecs = NULL;
2249 ti->graphics_set_aga = NULL;
2250 ti->graphics_set = NULL;
2251 ti->sounds_set = NULL;
2252 ti->music_set = NULL;
2253 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2254 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2255 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2257 ti->level_filename = NULL;
2258 ti->level_filetype = NULL;
2260 ti->special_flags = NULL;
2263 ti->first_level = 0;
2265 ti->level_group = FALSE;
2266 ti->handicap_level = 0;
2267 ti->readonly = TRUE;
2268 ti->handicap = TRUE;
2269 ti->skip_levels = FALSE;
2273 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2277 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2279 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2284 /* copy all values from the parent structure */
2286 ti->type = parent->type;
2288 ti->node_top = parent->node_top;
2289 ti->node_parent = parent;
2290 ti->node_group = NULL;
2297 ti->fullpath = NULL;
2298 ti->basepath = NULL;
2299 ti->identifier = NULL;
2300 ti->name = getStringCopy(ANONYMOUS_NAME);
2301 ti->name_sorting = NULL;
2302 ti->author = getStringCopy(parent->author);
2303 ti->year = getStringCopy(parent->year);
2305 ti->sort_priority = parent->sort_priority;
2306 ti->latest_engine = parent->latest_engine;
2307 ti->parent_link = FALSE;
2308 ti->in_user_dir = parent->in_user_dir;
2309 ti->user_defined = parent->user_defined;
2310 ti->color = parent->color;
2311 ti->class_desc = getStringCopy(parent->class_desc);
2313 ti->infotext = getStringCopy(parent->infotext);
2315 if (ti->type == TREE_TYPE_LEVEL_DIR)
2317 ti->imported_from = getStringCopy(parent->imported_from);
2318 ti->imported_by = getStringCopy(parent->imported_by);
2319 ti->tested_by = getStringCopy(parent->tested_by);
2321 ti->graphics_set_ecs = NULL;
2322 ti->graphics_set_aga = NULL;
2323 ti->graphics_set = NULL;
2324 ti->sounds_set = NULL;
2325 ti->music_set = NULL;
2326 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2327 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2328 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2330 ti->level_filename = NULL;
2331 ti->level_filetype = NULL;
2333 ti->special_flags = getStringCopy(parent->special_flags);
2336 ti->first_level = 0;
2338 ti->level_group = FALSE;
2339 ti->handicap_level = 0;
2340 ti->readonly = parent->readonly;
2341 ti->handicap = TRUE;
2342 ti->skip_levels = FALSE;
2346 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2348 TreeInfo *ti_copy = newTreeInfo();
2350 /* copy all values from the original structure */
2352 ti_copy->type = ti->type;
2354 ti_copy->node_top = ti->node_top;
2355 ti_copy->node_parent = ti->node_parent;
2356 ti_copy->node_group = ti->node_group;
2357 ti_copy->next = ti->next;
2359 ti_copy->cl_first = ti->cl_first;
2360 ti_copy->cl_cursor = ti->cl_cursor;
2362 ti_copy->subdir = getStringCopy(ti->subdir);
2363 ti_copy->fullpath = getStringCopy(ti->fullpath);
2364 ti_copy->basepath = getStringCopy(ti->basepath);
2365 ti_copy->identifier = getStringCopy(ti->identifier);
2366 ti_copy->name = getStringCopy(ti->name);
2367 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2368 ti_copy->author = getStringCopy(ti->author);
2369 ti_copy->year = getStringCopy(ti->year);
2370 ti_copy->imported_from = getStringCopy(ti->imported_from);
2371 ti_copy->imported_by = getStringCopy(ti->imported_by);
2372 ti_copy->tested_by = getStringCopy(ti->tested_by);
2374 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2375 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2376 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2377 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2378 ti_copy->music_set = getStringCopy(ti->music_set);
2379 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2380 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2381 ti_copy->music_path = getStringCopy(ti->music_path);
2383 ti_copy->level_filename = getStringCopy(ti->level_filename);
2384 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2386 ti_copy->special_flags = getStringCopy(ti->special_flags);
2388 ti_copy->levels = ti->levels;
2389 ti_copy->first_level = ti->first_level;
2390 ti_copy->last_level = ti->last_level;
2391 ti_copy->sort_priority = ti->sort_priority;
2393 ti_copy->latest_engine = ti->latest_engine;
2395 ti_copy->level_group = ti->level_group;
2396 ti_copy->parent_link = ti->parent_link;
2397 ti_copy->in_user_dir = ti->in_user_dir;
2398 ti_copy->user_defined = ti->user_defined;
2399 ti_copy->readonly = ti->readonly;
2400 ti_copy->handicap = ti->handicap;
2401 ti_copy->skip_levels = ti->skip_levels;
2403 ti_copy->color = ti->color;
2404 ti_copy->class_desc = getStringCopy(ti->class_desc);
2405 ti_copy->handicap_level = ti->handicap_level;
2407 ti_copy->infotext = getStringCopy(ti->infotext);
2412 void freeTreeInfo(TreeInfo *ti)
2417 checked_free(ti->subdir);
2418 checked_free(ti->fullpath);
2419 checked_free(ti->basepath);
2420 checked_free(ti->identifier);
2422 checked_free(ti->name);
2423 checked_free(ti->name_sorting);
2424 checked_free(ti->author);
2425 checked_free(ti->year);
2427 checked_free(ti->class_desc);
2429 checked_free(ti->infotext);
2431 if (ti->type == TREE_TYPE_LEVEL_DIR)
2433 checked_free(ti->imported_from);
2434 checked_free(ti->imported_by);
2435 checked_free(ti->tested_by);
2437 checked_free(ti->graphics_set_ecs);
2438 checked_free(ti->graphics_set_aga);
2439 checked_free(ti->graphics_set);
2440 checked_free(ti->sounds_set);
2441 checked_free(ti->music_set);
2443 checked_free(ti->graphics_path);
2444 checked_free(ti->sounds_path);
2445 checked_free(ti->music_path);
2447 checked_free(ti->level_filename);
2448 checked_free(ti->level_filetype);
2450 checked_free(ti->special_flags);
2453 // recursively free child node
2455 freeTreeInfo(ti->node_group);
2457 // recursively free next node
2459 freeTreeInfo(ti->next);
2464 void setSetupInfo(struct TokenInfo *token_info,
2465 int token_nr, char *token_value)
2467 int token_type = token_info[token_nr].type;
2468 void *setup_value = token_info[token_nr].value;
2470 if (token_value == NULL)
2473 /* set setup field to corresponding token value */
2478 *(boolean *)setup_value = get_boolean_from_string(token_value);
2482 *(int *)setup_value = get_switch3_from_string(token_value);
2486 *(Key *)setup_value = getKeyFromKeyName(token_value);
2490 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2494 *(int *)setup_value = get_integer_from_string(token_value);
2498 checked_free(*(char **)setup_value);
2499 *(char **)setup_value = getStringCopy(token_value);
2507 static int compareTreeInfoEntries(const void *object1, const void *object2)
2509 const TreeInfo *entry1 = *((TreeInfo **)object1);
2510 const TreeInfo *entry2 = *((TreeInfo **)object2);
2511 int class_sorting1 = 0, class_sorting2 = 0;
2514 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2516 class_sorting1 = LEVELSORTING(entry1);
2517 class_sorting2 = LEVELSORTING(entry2);
2519 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2520 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2521 entry1->type == TREE_TYPE_MUSIC_DIR)
2523 class_sorting1 = ARTWORKSORTING(entry1);
2524 class_sorting2 = ARTWORKSORTING(entry2);
2527 if (entry1->parent_link || entry2->parent_link)
2528 compare_result = (entry1->parent_link ? -1 : +1);
2529 else if (entry1->sort_priority == entry2->sort_priority)
2531 char *name1 = getStringToLower(entry1->name_sorting);
2532 char *name2 = getStringToLower(entry2->name_sorting);
2534 compare_result = strcmp(name1, name2);
2539 else if (class_sorting1 == class_sorting2)
2540 compare_result = entry1->sort_priority - entry2->sort_priority;
2542 compare_result = class_sorting1 - class_sorting2;
2544 return compare_result;
2547 static void createParentTreeInfoNode(TreeInfo *node_parent)
2551 if (node_parent == NULL)
2554 ti_new = newTreeInfo();
2555 setTreeInfoToDefaults(ti_new, node_parent->type);
2557 ti_new->node_parent = node_parent;
2558 ti_new->parent_link = TRUE;
2560 setString(&ti_new->identifier, node_parent->identifier);
2561 setString(&ti_new->name, ".. (parent directory)");
2562 setString(&ti_new->name_sorting, ti_new->name);
2564 setString(&ti_new->subdir, "..");
2565 setString(&ti_new->fullpath, node_parent->fullpath);
2567 ti_new->sort_priority = node_parent->sort_priority;
2568 ti_new->latest_engine = node_parent->latest_engine;
2570 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2572 pushTreeInfo(&node_parent->node_group, ti_new);
2576 /* -------------------------------------------------------------------------- */
2577 /* functions for handling level and custom artwork info cache */
2578 /* -------------------------------------------------------------------------- */
2580 static void LoadArtworkInfoCache()
2582 InitCacheDirectory();
2584 if (artworkinfo_cache_old == NULL)
2586 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2588 /* try to load artwork info hash from already existing cache file */
2589 artworkinfo_cache_old = loadSetupFileHash(filename);
2591 /* if no artwork info cache file was found, start with empty hash */
2592 if (artworkinfo_cache_old == NULL)
2593 artworkinfo_cache_old = newSetupFileHash();
2598 if (artworkinfo_cache_new == NULL)
2599 artworkinfo_cache_new = newSetupFileHash();
2602 static void SaveArtworkInfoCache()
2604 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2606 InitCacheDirectory();
2608 saveSetupFileHash(artworkinfo_cache_new, filename);
2613 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2615 static char *prefix = NULL;
2617 checked_free(prefix);
2619 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2624 /* (identical to above function, but separate string buffer needed -- nasty) */
2625 static char *getCacheToken(char *prefix, char *suffix)
2627 static char *token = NULL;
2629 checked_free(token);
2631 token = getStringCat2WithSeparator(prefix, suffix, ".");
2636 static char *getFileTimestampString(char *filename)
2638 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2641 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2643 struct stat file_status;
2645 if (timestamp_string == NULL)
2648 if (stat(filename, &file_status) != 0) /* cannot stat file */
2651 return (file_status.st_mtime != atoi(timestamp_string));
2654 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2656 char *identifier = level_node->subdir;
2657 char *type_string = ARTWORK_DIRECTORY(type);
2658 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2659 char *token_main = getCacheToken(token_prefix, "CACHED");
2660 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2661 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2662 TreeInfo *artwork_info = NULL;
2664 if (!use_artworkinfo_cache)
2671 artwork_info = newTreeInfo();
2672 setTreeInfoToDefaults(artwork_info, type);
2674 /* set all structure fields according to the token/value pairs */
2675 ldi = *artwork_info;
2676 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2678 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2679 char *value = getHashEntry(artworkinfo_cache_old, token);
2681 setSetupInfo(artworkinfo_tokens, i, value);
2683 /* check if cache entry for this item is invalid or incomplete */
2686 Error(ERR_WARN, "cache entry '%s' invalid", token);
2692 *artwork_info = ldi;
2697 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2698 LEVELINFO_FILENAME);
2699 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2700 ARTWORKINFO_FILENAME(type));
2702 /* check if corresponding "levelinfo.conf" file has changed */
2703 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2704 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2706 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2709 /* check if corresponding "<artworkinfo>.conf" file has changed */
2710 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2711 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2713 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2716 checked_free(filename_levelinfo);
2717 checked_free(filename_artworkinfo);
2720 if (!cached && artwork_info != NULL)
2722 freeTreeInfo(artwork_info);
2727 return artwork_info;
2730 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2731 LevelDirTree *level_node, int type)
2733 char *identifier = level_node->subdir;
2734 char *type_string = ARTWORK_DIRECTORY(type);
2735 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2736 char *token_main = getCacheToken(token_prefix, "CACHED");
2737 boolean set_cache_timestamps = TRUE;
2740 setHashEntry(artworkinfo_cache_new, token_main, "true");
2742 if (set_cache_timestamps)
2744 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2745 LEVELINFO_FILENAME);
2746 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2747 ARTWORKINFO_FILENAME(type));
2748 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2749 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2751 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2752 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2754 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2755 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2757 checked_free(filename_levelinfo);
2758 checked_free(filename_artworkinfo);
2759 checked_free(timestamp_levelinfo);
2760 checked_free(timestamp_artworkinfo);
2763 ldi = *artwork_info;
2764 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2766 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2767 char *value = getSetupValue(artworkinfo_tokens[i].type,
2768 artworkinfo_tokens[i].value);
2770 setHashEntry(artworkinfo_cache_new, token, value);
2775 /* -------------------------------------------------------------------------- */
2776 /* functions for loading level info and custom artwork info */
2777 /* -------------------------------------------------------------------------- */
2779 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2780 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2782 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2783 TreeInfo *node_parent,
2784 char *level_directory,
2785 char *directory_name)
2787 char *directory_path = getPath2(level_directory, directory_name);
2788 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2789 SetupFileHash *setup_file_hash;
2790 LevelDirTree *leveldir_new = NULL;
2793 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2794 if (!options.debug && !fileExists(filename))
2796 free(directory_path);
2802 setup_file_hash = loadSetupFileHash(filename);
2804 if (setup_file_hash == NULL)
2806 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2808 free(directory_path);
2814 leveldir_new = newTreeInfo();
2817 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2819 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2821 leveldir_new->subdir = getStringCopy(directory_name);
2823 checkSetupFileHashIdentifier(setup_file_hash, filename,
2824 getCookie("LEVELINFO"));
2826 /* set all structure fields according to the token/value pairs */
2827 ldi = *leveldir_new;
2828 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2829 setSetupInfo(levelinfo_tokens, i,
2830 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2831 *leveldir_new = ldi;
2833 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2834 setString(&leveldir_new->name, leveldir_new->subdir);
2836 if (leveldir_new->identifier == NULL)
2837 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2839 if (leveldir_new->name_sorting == NULL)
2840 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2842 if (node_parent == NULL) /* top level group */
2844 leveldir_new->basepath = getStringCopy(level_directory);
2845 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2847 else /* sub level group */
2849 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2850 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2853 leveldir_new->last_level =
2854 leveldir_new->first_level + leveldir_new->levels - 1;
2856 leveldir_new->in_user_dir =
2857 (!strEqual(leveldir_new->basepath, options.level_directory));
2859 /* adjust some settings if user's private level directory was detected */
2860 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2861 leveldir_new->in_user_dir &&
2862 (strEqual(leveldir_new->subdir, getLoginName()) ||
2863 strEqual(leveldir_new->name, getLoginName()) ||
2864 strEqual(leveldir_new->author, getRealName())))
2866 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2867 leveldir_new->readonly = FALSE;
2870 leveldir_new->user_defined =
2871 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2873 leveldir_new->color = LEVELCOLOR(leveldir_new);
2875 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2877 leveldir_new->handicap_level = /* set handicap to default value */
2878 (leveldir_new->user_defined || !leveldir_new->handicap ?
2879 leveldir_new->last_level : leveldir_new->first_level);
2881 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2883 pushTreeInfo(node_first, leveldir_new);
2885 freeSetupFileHash(setup_file_hash);
2887 if (leveldir_new->level_group)
2889 /* create node to link back to current level directory */
2890 createParentTreeInfoNode(leveldir_new);
2892 /* recursively step into sub-directory and look for more level series */
2893 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2894 leveldir_new, directory_path);
2897 free(directory_path);
2903 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2904 TreeInfo *node_parent,
2905 char *level_directory)
2908 DirectoryEntry *dir_entry;
2909 boolean valid_entry_found = FALSE;
2911 if ((dir = openDirectory(level_directory)) == NULL)
2913 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2918 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2920 char *directory_name = dir_entry->basename;
2921 char *directory_path = getPath2(level_directory, directory_name);
2923 /* skip entries for current and parent directory */
2924 if (strEqual(directory_name, ".") ||
2925 strEqual(directory_name, ".."))
2927 free(directory_path);
2932 /* find out if directory entry is itself a directory */
2933 if (!dir_entry->is_directory) /* not a directory */
2935 free(directory_path);
2940 free(directory_path);
2942 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2943 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2944 strEqual(directory_name, MUSIC_DIRECTORY))
2947 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2952 closeDirectory(dir);
2954 /* special case: top level directory may directly contain "levelinfo.conf" */
2955 if (node_parent == NULL && !valid_entry_found)
2957 /* check if this directory directly contains a file "levelinfo.conf" */
2958 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2959 level_directory, ".");
2962 if (!valid_entry_found)
2963 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2967 boolean AdjustGraphicsForEMC()
2969 boolean settings_changed = FALSE;
2971 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2972 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2974 return settings_changed;
2977 void LoadLevelInfo()
2979 InitUserLevelDirectory(getLoginName());
2981 DrawInitText("Loading level series", 120, FC_GREEN);
2983 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2984 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2986 /* after loading all level set information, clone the level directory tree
2987 and remove all level sets without levels (these may still contain artwork
2988 to be offered in the setup menu as "custom artwork", and are therefore
2989 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2990 leveldir_first_all = leveldir_first;
2991 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2993 AdjustGraphicsForEMC();
2995 /* before sorting, the first entries will be from the user directory */
2996 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2998 if (leveldir_first == NULL)
2999 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3001 sortTreeInfo(&leveldir_first);
3003 #if ENABLE_UNUSED_CODE
3004 dumpTreeInfo(leveldir_first, 0);
3008 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3009 TreeInfo *node_parent,
3010 char *base_directory,
3011 char *directory_name, int type)
3013 char *directory_path = getPath2(base_directory, directory_name);
3014 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3015 SetupFileHash *setup_file_hash = NULL;
3016 TreeInfo *artwork_new = NULL;
3019 if (fileExists(filename))
3020 setup_file_hash = loadSetupFileHash(filename);
3022 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3025 DirectoryEntry *dir_entry;
3026 boolean valid_file_found = FALSE;
3028 if ((dir = openDirectory(directory_path)) != NULL)
3030 while ((dir_entry = readDirectory(dir)) != NULL)
3032 if (FileIsArtworkType(dir_entry->filename, type))
3034 valid_file_found = TRUE;
3040 closeDirectory(dir);
3043 if (!valid_file_found)
3045 if (!strEqual(directory_name, "."))
3046 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3048 free(directory_path);
3055 artwork_new = newTreeInfo();
3058 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3060 setTreeInfoToDefaults(artwork_new, type);
3062 artwork_new->subdir = getStringCopy(directory_name);
3064 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3066 /* set all structure fields according to the token/value pairs */
3068 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3069 setSetupInfo(levelinfo_tokens, i,
3070 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3073 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3074 setString(&artwork_new->name, artwork_new->subdir);
3076 if (artwork_new->identifier == NULL)
3077 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3079 if (artwork_new->name_sorting == NULL)
3080 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3083 if (node_parent == NULL) /* top level group */
3085 artwork_new->basepath = getStringCopy(base_directory);
3086 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3088 else /* sub level group */
3090 artwork_new->basepath = getStringCopy(node_parent->basepath);
3091 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3094 artwork_new->in_user_dir =
3095 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3097 /* (may use ".sort_priority" from "setup_file_hash" above) */
3098 artwork_new->color = ARTWORKCOLOR(artwork_new);
3100 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3102 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3104 if (strEqual(artwork_new->subdir, "."))
3106 if (artwork_new->user_defined)
3108 setString(&artwork_new->identifier, "private");
3109 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3113 setString(&artwork_new->identifier, "classic");
3114 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3117 /* set to new values after changing ".sort_priority" */
3118 artwork_new->color = ARTWORKCOLOR(artwork_new);
3120 setString(&artwork_new->class_desc,
3121 getLevelClassDescription(artwork_new));
3125 setString(&artwork_new->identifier, artwork_new->subdir);
3128 setString(&artwork_new->name, artwork_new->identifier);
3129 setString(&artwork_new->name_sorting, artwork_new->name);
3132 pushTreeInfo(node_first, artwork_new);
3134 freeSetupFileHash(setup_file_hash);
3136 free(directory_path);
3142 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3143 TreeInfo *node_parent,
3144 char *base_directory, int type)
3147 DirectoryEntry *dir_entry;
3148 boolean valid_entry_found = FALSE;
3150 if ((dir = openDirectory(base_directory)) == NULL)
3152 /* display error if directory is main "options.graphics_directory" etc. */
3153 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3154 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3159 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3161 char *directory_name = dir_entry->basename;
3162 char *directory_path = getPath2(base_directory, directory_name);
3164 /* skip directory entries for current and parent directory */
3165 if (strEqual(directory_name, ".") ||
3166 strEqual(directory_name, ".."))
3168 free(directory_path);
3173 /* skip directory entries which are not a directory */
3174 if (!dir_entry->is_directory) /* not a directory */
3176 free(directory_path);
3181 free(directory_path);
3183 /* check if this directory contains artwork with or without config file */
3184 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3186 directory_name, type);
3189 closeDirectory(dir);
3191 /* check if this directory directly contains artwork itself */
3192 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3193 base_directory, ".",
3195 if (!valid_entry_found)
3196 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3200 static TreeInfo *getDummyArtworkInfo(int type)
3202 /* this is only needed when there is completely no artwork available */
3203 TreeInfo *artwork_new = newTreeInfo();
3205 setTreeInfoToDefaults(artwork_new, type);
3207 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3208 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3209 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3211 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3212 setString(&artwork_new->name, UNDEFINED_FILENAME);
3213 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3218 void LoadArtworkInfo()
3220 LoadArtworkInfoCache();
3222 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3224 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3225 options.graphics_directory,
3226 TREE_TYPE_GRAPHICS_DIR);
3227 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3228 getUserGraphicsDir(),
3229 TREE_TYPE_GRAPHICS_DIR);
3231 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3232 options.sounds_directory,
3233 TREE_TYPE_SOUNDS_DIR);
3234 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3236 TREE_TYPE_SOUNDS_DIR);
3238 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3239 options.music_directory,
3240 TREE_TYPE_MUSIC_DIR);
3241 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3243 TREE_TYPE_MUSIC_DIR);
3245 if (artwork.gfx_first == NULL)
3246 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3247 if (artwork.snd_first == NULL)
3248 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3249 if (artwork.mus_first == NULL)
3250 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3252 /* before sorting, the first entries will be from the user directory */
3253 artwork.gfx_current =
3254 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3255 if (artwork.gfx_current == NULL)
3256 artwork.gfx_current =
3257 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3258 if (artwork.gfx_current == NULL)
3259 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3261 artwork.snd_current =
3262 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3263 if (artwork.snd_current == NULL)
3264 artwork.snd_current =
3265 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3266 if (artwork.snd_current == NULL)
3267 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3269 artwork.mus_current =
3270 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3271 if (artwork.mus_current == NULL)
3272 artwork.mus_current =
3273 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3274 if (artwork.mus_current == NULL)
3275 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3277 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3278 artwork.snd_current_identifier = artwork.snd_current->identifier;
3279 artwork.mus_current_identifier = artwork.mus_current->identifier;
3281 #if ENABLE_UNUSED_CODE
3282 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3283 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3284 printf("music set == %s\n\n", artwork.mus_current_identifier);
3287 sortTreeInfo(&artwork.gfx_first);
3288 sortTreeInfo(&artwork.snd_first);
3289 sortTreeInfo(&artwork.mus_first);
3291 #if ENABLE_UNUSED_CODE
3292 dumpTreeInfo(artwork.gfx_first, 0);
3293 dumpTreeInfo(artwork.snd_first, 0);
3294 dumpTreeInfo(artwork.mus_first, 0);
3298 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3299 LevelDirTree *level_node)
3301 int type = (*artwork_node)->type;
3303 /* recursively check all level directories for artwork sub-directories */
3307 /* check all tree entries for artwork, but skip parent link entries */
3308 if (!level_node->parent_link)
3310 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3311 boolean cached = (artwork_new != NULL);
3315 pushTreeInfo(artwork_node, artwork_new);
3319 TreeInfo *topnode_last = *artwork_node;
3320 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3321 ARTWORK_DIRECTORY(type));
3323 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3325 if (topnode_last != *artwork_node) /* check for newly added node */
3327 artwork_new = *artwork_node;
3329 setString(&artwork_new->identifier, level_node->subdir);
3330 setString(&artwork_new->name, level_node->name);
3331 setString(&artwork_new->name_sorting, level_node->name_sorting);
3333 artwork_new->sort_priority = level_node->sort_priority;
3334 artwork_new->color = LEVELCOLOR(artwork_new);
3340 /* insert artwork info (from old cache or filesystem) into new cache */
3341 if (artwork_new != NULL)
3342 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3345 DrawInitText(level_node->name, 150, FC_YELLOW);
3347 if (level_node->node_group != NULL)
3348 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3350 level_node = level_node->next;
3354 void LoadLevelArtworkInfo()
3356 print_timestamp_init("LoadLevelArtworkInfo");
3358 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3360 print_timestamp_time("DrawTimeText");
3362 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3363 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3364 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3365 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3366 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3367 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3369 SaveArtworkInfoCache();
3371 print_timestamp_time("SaveArtworkInfoCache");
3373 /* needed for reloading level artwork not known at ealier stage */
3375 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3377 artwork.gfx_current =
3378 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3379 if (artwork.gfx_current == NULL)
3380 artwork.gfx_current =
3381 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3382 if (artwork.gfx_current == NULL)
3383 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3386 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3388 artwork.snd_current =
3389 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3390 if (artwork.snd_current == NULL)
3391 artwork.snd_current =
3392 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3393 if (artwork.snd_current == NULL)
3394 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3397 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3399 artwork.mus_current =
3400 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3401 if (artwork.mus_current == NULL)
3402 artwork.mus_current =
3403 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3404 if (artwork.mus_current == NULL)
3405 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3408 print_timestamp_time("getTreeInfoFromIdentifier");
3410 sortTreeInfo(&artwork.gfx_first);
3411 sortTreeInfo(&artwork.snd_first);
3412 sortTreeInfo(&artwork.mus_first);
3414 print_timestamp_time("sortTreeInfo");
3416 #if ENABLE_UNUSED_CODE
3417 dumpTreeInfo(artwork.gfx_first, 0);
3418 dumpTreeInfo(artwork.snd_first, 0);
3419 dumpTreeInfo(artwork.mus_first, 0);
3422 print_timestamp_done("LoadLevelArtworkInfo");
3425 static void SaveUserLevelInfo()
3427 LevelDirTree *level_info;
3432 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3434 if (!(file = fopen(filename, MODE_WRITE)))
3436 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3441 level_info = newTreeInfo();
3443 /* always start with reliable default values */
3444 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3446 setString(&level_info->name, getLoginName());
3447 setString(&level_info->author, getRealName());
3448 level_info->levels = 100;
3449 level_info->first_level = 1;
3451 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3453 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3454 getCookie("LEVELINFO")));
3457 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3459 if (i == LEVELINFO_TOKEN_NAME ||
3460 i == LEVELINFO_TOKEN_AUTHOR ||
3461 i == LEVELINFO_TOKEN_LEVELS ||
3462 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3463 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3465 /* just to make things nicer :) */
3466 if (i == LEVELINFO_TOKEN_AUTHOR)
3467 fprintf(file, "\n");
3470 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3474 SetFilePermissions(filename, PERMS_PRIVATE);
3476 freeTreeInfo(level_info);
3480 char *getSetupValue(int type, void *value)
3482 static char value_string[MAX_LINE_LEN];
3490 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3494 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3498 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3499 *(int *)value == FALSE ? "off" : "on"));
3503 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3506 case TYPE_YES_NO_AUTO:
3507 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3508 *(int *)value == FALSE ? "no" : "yes"));
3512 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3516 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3520 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3524 sprintf(value_string, "%d", *(int *)value);
3528 if (*(char **)value == NULL)
3531 strcpy(value_string, *(char **)value);
3535 value_string[0] = '\0';
3539 if (type & TYPE_GHOSTED)
3540 strcpy(value_string, "n/a");
3542 return value_string;
3545 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3549 static char token_string[MAX_LINE_LEN];
3550 int token_type = token_info[token_nr].type;
3551 void *setup_value = token_info[token_nr].value;
3552 char *token_text = token_info[token_nr].text;
3553 char *value_string = getSetupValue(token_type, setup_value);
3555 /* build complete token string */
3556 sprintf(token_string, "%s%s", prefix, token_text);
3558 /* build setup entry line */
3559 line = getFormattedSetupEntry(token_string, value_string);
3561 if (token_type == TYPE_KEY_X11)
3563 Key key = *(Key *)setup_value;
3564 char *keyname = getKeyNameFromKey(key);
3566 /* add comment, if useful */
3567 if (!strEqual(keyname, "(undefined)") &&
3568 !strEqual(keyname, "(unknown)"))
3570 /* add at least one whitespace */
3572 for (i = strlen(line); i < token_comment_position; i++)
3576 strcat(line, keyname);
3583 void LoadLevelSetup_LastSeries()
3585 /* ----------------------------------------------------------------------- */
3586 /* ~/.<program>/levelsetup.conf */
3587 /* ----------------------------------------------------------------------- */
3589 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3590 SetupFileHash *level_setup_hash = NULL;
3592 /* always start with reliable default values */
3593 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3595 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3596 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3598 if (leveldir_current == NULL)
3599 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3602 if ((level_setup_hash = loadSetupFileHash(filename)))
3604 char *last_level_series =
3605 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3607 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3609 if (leveldir_current == NULL)
3610 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3612 checkSetupFileHashIdentifier(level_setup_hash, filename,
3613 getCookie("LEVELSETUP"));
3615 freeSetupFileHash(level_setup_hash);
3618 Error(ERR_WARN, "using default setup values");
3623 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3625 /* ----------------------------------------------------------------------- */
3626 /* ~/.<program>/levelsetup.conf */
3627 /* ----------------------------------------------------------------------- */
3629 // check if the current level directory structure is available at this point
3630 if (leveldir_current == NULL)
3633 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3634 char *level_subdir = leveldir_current->subdir;
3637 InitUserDataDirectory();
3639 if (!(file = fopen(filename, MODE_WRITE)))
3641 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3648 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3649 getCookie("LEVELSETUP")));
3651 if (deactivate_last_level_series)
3652 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3654 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3659 SetFilePermissions(filename, PERMS_PRIVATE);
3664 void SaveLevelSetup_LastSeries()
3666 SaveLevelSetup_LastSeries_Ext(FALSE);
3669 void SaveLevelSetup_LastSeries_Deactivate()
3671 SaveLevelSetup_LastSeries_Ext(TRUE);
3674 static void checkSeriesInfo()
3676 static char *level_directory = NULL;
3679 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3681 level_directory = getPath2((leveldir_current->in_user_dir ?
3682 getUserLevelDir(NULL) :
3683 options.level_directory),
3684 leveldir_current->fullpath);
3686 if ((dir = openDirectory(level_directory)) == NULL)
3688 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3693 closeDirectory(dir);
3696 void LoadLevelSetup_SeriesInfo()
3699 SetupFileHash *level_setup_hash = NULL;
3700 char *level_subdir = leveldir_current->subdir;
3703 /* always start with reliable default values */
3704 level_nr = leveldir_current->first_level;
3706 for (i = 0; i < MAX_LEVELS; i++)
3708 LevelStats_setPlayed(i, 0);
3709 LevelStats_setSolved(i, 0);
3712 checkSeriesInfo(leveldir_current);
3714 /* ----------------------------------------------------------------------- */
3715 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3716 /* ----------------------------------------------------------------------- */
3718 level_subdir = leveldir_current->subdir;
3720 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3722 if ((level_setup_hash = loadSetupFileHash(filename)))
3726 /* get last played level in this level set */
3728 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3732 level_nr = atoi(token_value);
3734 if (level_nr < leveldir_current->first_level)
3735 level_nr = leveldir_current->first_level;
3736 if (level_nr > leveldir_current->last_level)
3737 level_nr = leveldir_current->last_level;
3740 /* get handicap level in this level set */
3742 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3746 int level_nr = atoi(token_value);
3748 if (level_nr < leveldir_current->first_level)
3749 level_nr = leveldir_current->first_level;
3750 if (level_nr > leveldir_current->last_level + 1)
3751 level_nr = leveldir_current->last_level;
3753 if (leveldir_current->user_defined || !leveldir_current->handicap)
3754 level_nr = leveldir_current->last_level;
3756 leveldir_current->handicap_level = level_nr;
3759 /* get number of played and solved levels in this level set */
3761 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3763 char *token = HASH_ITERATION_TOKEN(itr);
3764 char *value = HASH_ITERATION_VALUE(itr);
3766 if (strlen(token) == 3 &&
3767 token[0] >= '0' && token[0] <= '9' &&
3768 token[1] >= '0' && token[1] <= '9' &&
3769 token[2] >= '0' && token[2] <= '9')
3771 int level_nr = atoi(token);
3774 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3776 value = strchr(value, ' ');
3779 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3782 END_HASH_ITERATION(hash, itr)
3784 checkSetupFileHashIdentifier(level_setup_hash, filename,
3785 getCookie("LEVELSETUP"));
3787 freeSetupFileHash(level_setup_hash);
3790 Error(ERR_WARN, "using default setup values");
3795 void SaveLevelSetup_SeriesInfo()
3798 char *level_subdir = leveldir_current->subdir;
3799 char *level_nr_str = int2str(level_nr, 0);
3800 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3804 /* ----------------------------------------------------------------------- */
3805 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3806 /* ----------------------------------------------------------------------- */
3808 InitLevelSetupDirectory(level_subdir);
3810 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3812 if (!(file = fopen(filename, MODE_WRITE)))
3814 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3819 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3820 getCookie("LEVELSETUP")));
3821 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3823 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3824 handicap_level_str));
3826 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3829 if (LevelStats_getPlayed(i) > 0 ||
3830 LevelStats_getSolved(i) > 0)
3835 sprintf(token, "%03d", i);
3836 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3838 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3844 SetFilePermissions(filename, PERMS_PRIVATE);
3849 int LevelStats_getPlayed(int nr)
3851 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3854 int LevelStats_getSolved(int nr)
3856 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3859 void LevelStats_setPlayed(int nr, int value)
3861 if (nr >= 0 && nr < MAX_LEVELS)
3862 level_stats[nr].played = value;
3865 void LevelStats_setSolved(int nr, int value)
3867 if (nr >= 0 && nr < MAX_LEVELS)
3868 level_stats[nr].solved = value;
3871 void LevelStats_incPlayed(int nr)
3873 if (nr >= 0 && nr < MAX_LEVELS)
3874 level_stats[nr].played++;
3877 void LevelStats_incSolved(int nr)
3879 if (nr >= 0 && nr < MAX_LEVELS)
3880 level_stats[nr].solved++;