1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
23 #if !defined(PLATFORM_WIN32)
25 #include <sys/param.h>
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 char *data_dir = getCommonDataDir();
137 char *score_subdir = SCORES_DIRECTORY;
139 checked_free(score_dir);
141 if (level_subdir != NULL)
142 score_dir = getPath3(data_dir, score_subdir, level_subdir);
144 score_dir = getPath2(data_dir, score_subdir);
149 static char *getLevelSetupDir(char *level_subdir)
151 static char *levelsetup_dir = NULL;
152 char *data_dir = getUserGameDataDir();
153 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
155 checked_free(levelsetup_dir);
157 if (level_subdir != NULL)
158 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
160 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
162 return levelsetup_dir;
165 static char *getCacheDir()
167 static char *cache_dir = NULL;
169 if (cache_dir == NULL)
170 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
175 static char *getLevelDirFromTreeInfo(TreeInfo *node)
177 static char *level_dir = NULL;
180 return options.level_directory;
182 checked_free(level_dir);
184 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
185 options.level_directory), node->fullpath);
190 char *getCurrentLevelDir()
192 return getLevelDirFromTreeInfo(leveldir_current);
195 static char *getTapeDir(char *level_subdir)
197 static char *tape_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *tape_subdir = TAPES_DIRECTORY;
201 checked_free(tape_dir);
203 if (level_subdir != NULL)
204 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
206 tape_dir = getPath2(data_dir, tape_subdir);
211 static char *getSolutionTapeDir()
213 static char *tape_dir = NULL;
214 char *data_dir = getCurrentLevelDir();
215 char *tape_subdir = TAPES_DIRECTORY;
217 checked_free(tape_dir);
219 tape_dir = getPath2(data_dir, tape_subdir);
224 static char *getDefaultGraphicsDir(char *graphics_subdir)
226 static char *graphics_dir = NULL;
228 if (graphics_subdir == NULL)
229 return options.graphics_directory;
231 checked_free(graphics_dir);
233 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
238 static char *getDefaultSoundsDir(char *sounds_subdir)
240 static char *sounds_dir = NULL;
242 if (sounds_subdir == NULL)
243 return options.sounds_directory;
245 checked_free(sounds_dir);
247 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
252 static char *getDefaultMusicDir(char *music_subdir)
254 static char *music_dir = NULL;
256 if (music_subdir == NULL)
257 return options.music_directory;
259 checked_free(music_dir);
261 music_dir = getPath2(options.music_directory, music_subdir);
266 static char *getClassicArtworkSet(int type)
268 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
269 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
270 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
273 static char *getClassicArtworkDir(int type)
275 return (type == TREE_TYPE_GRAPHICS_DIR ?
276 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_SOUNDS_DIR ?
278 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
279 type == TREE_TYPE_MUSIC_DIR ?
280 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
283 static char *getUserGraphicsDir()
285 static char *usergraphics_dir = NULL;
287 if (usergraphics_dir == NULL)
288 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
290 return usergraphics_dir;
293 static char *getUserSoundsDir()
295 static char *usersounds_dir = NULL;
297 if (usersounds_dir == NULL)
298 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
300 return usersounds_dir;
303 static char *getUserMusicDir()
305 static char *usermusic_dir = NULL;
307 if (usermusic_dir == NULL)
308 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
310 return usermusic_dir;
313 static char *getSetupArtworkDir(TreeInfo *ti)
315 static char *artwork_dir = NULL;
317 checked_free(artwork_dir);
319 artwork_dir = getPath2(ti->basepath, ti->fullpath);
324 char *setLevelArtworkDir(TreeInfo *ti)
326 char **artwork_path_ptr, **artwork_set_ptr;
327 TreeInfo *level_artwork;
329 if (ti == NULL || leveldir_current == NULL)
332 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
333 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
335 checked_free(*artwork_path_ptr);
337 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
339 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
344 No (or non-existing) artwork configured in "levelinfo.conf". This would
345 normally result in using the artwork configured in the setup menu. But
346 if an artwork subdirectory exists (which might contain custom artwork
347 or an artwork configuration file), this level artwork must be treated
348 as relative to the default "classic" artwork, not to the artwork that
349 is currently configured in the setup menu.
351 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
352 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
353 the real "classic" artwork from the original R'n'D (like "gfx_classic").
356 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
358 checked_free(*artwork_set_ptr);
360 if (directoryExists(dir))
362 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
363 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
367 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
368 *artwork_set_ptr = NULL;
374 return *artwork_set_ptr;
377 inline static char *getLevelArtworkSet(int type)
379 if (leveldir_current == NULL)
382 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
385 inline static char *getLevelArtworkDir(int type)
387 if (leveldir_current == NULL)
388 return UNDEFINED_FILENAME;
390 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
393 char *getTapeFilename(int nr)
395 static char *filename = NULL;
396 char basename[MAX_FILENAME_LEN];
398 checked_free(filename);
400 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
401 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
406 char *getSolutionTapeFilename(int nr)
408 static char *filename = NULL;
409 char basename[MAX_FILENAME_LEN];
411 checked_free(filename);
413 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
414 filename = getPath2(getSolutionTapeDir(), basename);
416 if (!fileExists(filename))
418 static char *filename_sln = NULL;
420 checked_free(filename_sln);
422 sprintf(basename, "%03d.sln", nr);
423 filename_sln = getPath2(getSolutionTapeDir(), basename);
425 if (fileExists(filename_sln))
432 char *getScoreFilename(int nr)
434 static char *filename = NULL;
435 char basename[MAX_FILENAME_LEN];
437 checked_free(filename);
439 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
440 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
445 char *getSetupFilename()
447 static char *filename = NULL;
449 checked_free(filename);
451 filename = getPath2(getSetupDir(), SETUP_FILENAME);
456 char *getEditorSetupFilename()
458 static char *filename = NULL;
460 checked_free(filename);
461 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
463 if (fileExists(filename))
466 checked_free(filename);
467 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
472 char *getHelpAnimFilename()
474 static char *filename = NULL;
476 checked_free(filename);
478 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
483 char *getHelpTextFilename()
485 static char *filename = NULL;
487 checked_free(filename);
489 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
494 char *getLevelSetInfoFilename()
496 static char *filename = NULL;
511 for (i = 0; basenames[i] != NULL; i++)
513 checked_free(filename);
514 filename = getPath2(getCurrentLevelDir(), basenames[i]);
516 if (fileExists(filename))
523 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
525 static char basename[32];
527 sprintf(basename, "%s_%d.txt",
528 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
533 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
535 static char *filename = NULL;
537 boolean skip_setup_artwork = FALSE;
539 checked_free(filename);
541 basename = getLevelSetTitleMessageBasename(nr, initial);
543 if (!gfx.override_level_graphics)
545 /* 1st try: look for special artwork in current level series directory */
546 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
547 if (fileExists(filename))
552 /* 2nd try: look for message file in current level set directory */
553 filename = getPath2(getCurrentLevelDir(), basename);
554 if (fileExists(filename))
559 /* check if there is special artwork configured in level series config */
560 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
562 /* 3rd try: look for special artwork configured in level series config */
563 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
564 if (fileExists(filename))
569 /* take missing artwork configured in level set config from default */
570 skip_setup_artwork = TRUE;
574 if (!skip_setup_artwork)
576 /* 4th try: look for special artwork in configured artwork directory */
577 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
578 if (fileExists(filename))
584 /* 5th try: look for default artwork in new default artwork directory */
585 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
586 if (fileExists(filename))
591 /* 6th try: look for default artwork in old default artwork directory */
592 filename = getPath2(options.graphics_directory, basename);
593 if (fileExists(filename))
596 return NULL; /* cannot find specified artwork file anywhere */
599 static char *getCorrectedArtworkBasename(char *basename)
604 char *getCustomImageFilename(char *basename)
606 static char *filename = NULL;
607 boolean skip_setup_artwork = FALSE;
609 checked_free(filename);
611 basename = getCorrectedArtworkBasename(basename);
613 if (!gfx.override_level_graphics)
615 /* 1st try: look for special artwork in current level series directory */
616 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
617 if (fileExists(filename))
622 /* check if there is special artwork configured in level series config */
623 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
625 /* 2nd try: look for special artwork configured in level series config */
626 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
627 if (fileExists(filename))
632 /* take missing artwork configured in level set config from default */
633 skip_setup_artwork = TRUE;
637 if (!skip_setup_artwork)
639 /* 3rd try: look for special artwork in configured artwork directory */
640 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
641 if (fileExists(filename))
647 /* 4th try: look for default artwork in new default artwork directory */
648 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
649 if (fileExists(filename))
654 /* 5th try: look for default artwork in old default artwork directory */
655 filename = getPath2(options.graphics_directory, basename);
656 if (fileExists(filename))
659 #if defined(CREATE_SPECIAL_EDITION)
663 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
665 /* 6th try: look for fallback artwork in old default artwork directory */
666 /* (needed to prevent errors when trying to access unused artwork files) */
667 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
668 if (fileExists(filename))
672 return NULL; /* cannot find specified artwork file anywhere */
675 char *getCustomSoundFilename(char *basename)
677 static char *filename = NULL;
678 boolean skip_setup_artwork = FALSE;
680 checked_free(filename);
682 basename = getCorrectedArtworkBasename(basename);
684 if (!gfx.override_level_sounds)
686 /* 1st try: look for special artwork in current level series directory */
687 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
688 if (fileExists(filename))
693 /* check if there is special artwork configured in level series config */
694 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
696 /* 2nd try: look for special artwork configured in level series config */
697 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
698 if (fileExists(filename))
703 /* take missing artwork configured in level set config from default */
704 skip_setup_artwork = TRUE;
708 if (!skip_setup_artwork)
710 /* 3rd try: look for special artwork in configured artwork directory */
711 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
712 if (fileExists(filename))
718 /* 4th try: look for default artwork in new default artwork directory */
719 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
720 if (fileExists(filename))
725 /* 5th try: look for default artwork in old default artwork directory */
726 filename = getPath2(options.sounds_directory, basename);
727 if (fileExists(filename))
730 #if defined(CREATE_SPECIAL_EDITION)
734 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
736 /* 6th try: look for fallback artwork in old default artwork directory */
737 /* (needed to prevent errors when trying to access unused artwork files) */
738 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
739 if (fileExists(filename))
743 return NULL; /* cannot find specified artwork file anywhere */
746 char *getCustomMusicFilename(char *basename)
748 static char *filename = NULL;
749 boolean skip_setup_artwork = FALSE;
751 checked_free(filename);
753 basename = getCorrectedArtworkBasename(basename);
755 if (!gfx.override_level_music)
757 /* 1st try: look for special artwork in current level series directory */
758 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
759 if (fileExists(filename))
764 /* check if there is special artwork configured in level series config */
765 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
767 /* 2nd try: look for special artwork configured in level series config */
768 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
769 if (fileExists(filename))
774 /* take missing artwork configured in level set config from default */
775 skip_setup_artwork = TRUE;
779 if (!skip_setup_artwork)
781 /* 3rd try: look for special artwork in configured artwork directory */
782 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
783 if (fileExists(filename))
789 /* 4th try: look for default artwork in new default artwork directory */
790 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
791 if (fileExists(filename))
796 /* 5th try: look for default artwork in old default artwork directory */
797 filename = getPath2(options.music_directory, basename);
798 if (fileExists(filename))
801 #if defined(CREATE_SPECIAL_EDITION)
805 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
807 /* 6th try: look for fallback artwork in old default artwork directory */
808 /* (needed to prevent errors when trying to access unused artwork files) */
809 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
810 if (fileExists(filename))
814 return NULL; /* cannot find specified artwork file anywhere */
817 char *getCustomArtworkFilename(char *basename, int type)
819 if (type == ARTWORK_TYPE_GRAPHICS)
820 return getCustomImageFilename(basename);
821 else if (type == ARTWORK_TYPE_SOUNDS)
822 return getCustomSoundFilename(basename);
823 else if (type == ARTWORK_TYPE_MUSIC)
824 return getCustomMusicFilename(basename);
826 return UNDEFINED_FILENAME;
829 char *getCustomArtworkConfigFilename(int type)
831 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
834 char *getCustomArtworkLevelConfigFilename(int type)
836 static char *filename = NULL;
838 checked_free(filename);
840 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
845 char *getCustomMusicDirectory(void)
847 static char *directory = NULL;
848 boolean skip_setup_artwork = FALSE;
850 checked_free(directory);
852 if (!gfx.override_level_music)
854 /* 1st try: look for special artwork in current level series directory */
855 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
856 if (directoryExists(directory))
861 /* check if there is special artwork configured in level series config */
862 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
864 /* 2nd try: look for special artwork configured in level series config */
865 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
866 if (directoryExists(directory))
871 /* take missing artwork configured in level set config from default */
872 skip_setup_artwork = TRUE;
876 if (!skip_setup_artwork)
878 /* 3rd try: look for special artwork in configured artwork directory */
879 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
880 if (directoryExists(directory))
886 /* 4th try: look for default artwork in new default artwork directory */
887 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
888 if (directoryExists(directory))
893 /* 5th try: look for default artwork in old default artwork directory */
894 directory = getStringCopy(options.music_directory);
895 if (directoryExists(directory))
898 return NULL; /* cannot find specified artwork file anywhere */
901 void InitTapeDirectory(char *level_subdir)
903 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
904 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
905 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
908 void InitScoreDirectory(char *level_subdir)
910 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
911 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
912 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
915 static void SaveUserLevelInfo();
917 void InitUserLevelDirectory(char *level_subdir)
919 if (!directoryExists(getUserLevelDir(level_subdir)))
921 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
922 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
923 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
929 void InitLevelSetupDirectory(char *level_subdir)
931 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
932 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
933 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
936 void InitCacheDirectory()
938 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
939 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
943 /* ------------------------------------------------------------------------- */
944 /* some functions to handle lists of level and artwork directories */
945 /* ------------------------------------------------------------------------- */
947 TreeInfo *newTreeInfo()
949 return checked_calloc(sizeof(TreeInfo));
952 TreeInfo *newTreeInfo_setDefaults(int type)
954 TreeInfo *ti = newTreeInfo();
956 setTreeInfoToDefaults(ti, type);
961 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
963 node_new->next = *node_first;
964 *node_first = node_new;
967 int numTreeInfo(TreeInfo *node)
980 boolean validLevelSeries(TreeInfo *node)
982 return (node != NULL && !node->node_group && !node->parent_link);
985 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
990 if (node->node_group) /* enter level group (step down into tree) */
991 return getFirstValidTreeInfoEntry(node->node_group);
992 else if (node->parent_link) /* skip start entry of level group */
994 if (node->next) /* get first real level series entry */
995 return getFirstValidTreeInfoEntry(node->next);
996 else /* leave empty level group and go on */
997 return getFirstValidTreeInfoEntry(node->node_parent->next);
999 else /* this seems to be a regular level series */
1003 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1008 if (node->node_parent == NULL) /* top level group */
1009 return *node->node_top;
1010 else /* sub level group */
1011 return node->node_parent->node_group;
1014 int numTreeInfoInGroup(TreeInfo *node)
1016 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1019 int posTreeInfo(TreeInfo *node)
1021 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1026 if (node_cmp == node)
1030 node_cmp = node_cmp->next;
1036 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1038 TreeInfo *node_default = node;
1050 return node_default;
1053 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1055 if (identifier == NULL)
1060 if (node->node_group)
1062 TreeInfo *node_group;
1064 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1069 else if (!node->parent_link)
1071 if (strEqual(identifier, node->identifier))
1081 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1082 TreeInfo *node, boolean skip_sets_without_levels)
1089 if (!node->parent_link && !node->level_group &&
1090 skip_sets_without_levels && node->levels == 0)
1091 return cloneTreeNode(node_top, node_parent, node->next,
1092 skip_sets_without_levels);
1095 node_new = getTreeInfoCopy(node); /* copy complete node */
1097 node_new = newTreeInfo();
1099 *node_new = *node; /* copy complete node */
1102 node_new->node_top = node_top; /* correct top node link */
1103 node_new->node_parent = node_parent; /* correct parent node link */
1105 if (node->level_group)
1106 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1107 skip_sets_without_levels);
1109 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1110 skip_sets_without_levels);
1115 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1117 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1119 *ti_new = ti_cloned;
1122 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1124 boolean settings_changed = FALSE;
1128 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1129 !strEqual(node->graphics_set, node->graphics_set_ecs))
1131 setString(&node->graphics_set, node->graphics_set_ecs);
1132 settings_changed = TRUE;
1134 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1135 !strEqual(node->graphics_set, node->graphics_set_aga))
1137 setString(&node->graphics_set, node->graphics_set_aga);
1138 settings_changed = TRUE;
1141 if (node->node_group != NULL)
1142 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1147 return settings_changed;
1150 void dumpTreeInfo(TreeInfo *node, int depth)
1154 printf("Dumping TreeInfo:\n");
1158 for (i = 0; i < (depth + 1) * 3; i++)
1161 printf("'%s' / '%s'\n", node->identifier, node->name);
1164 // use for dumping artwork info tree
1165 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1166 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1169 if (node->node_group != NULL)
1170 dumpTreeInfo(node->node_group, depth + 1);
1176 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1177 int (*compare_function)(const void *,
1180 int num_nodes = numTreeInfo(*node_first);
1181 TreeInfo **sort_array;
1182 TreeInfo *node = *node_first;
1188 /* allocate array for sorting structure pointers */
1189 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1191 /* writing structure pointers to sorting array */
1192 while (i < num_nodes && node) /* double boundary check... */
1194 sort_array[i] = node;
1200 /* sorting the structure pointers in the sorting array */
1201 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1204 /* update the linkage of list elements with the sorted node array */
1205 for (i = 0; i < num_nodes - 1; i++)
1206 sort_array[i]->next = sort_array[i + 1];
1207 sort_array[num_nodes - 1]->next = NULL;
1209 /* update the linkage of the main list anchor pointer */
1210 *node_first = sort_array[0];
1214 /* now recursively sort the level group structures */
1218 if (node->node_group != NULL)
1219 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1225 void sortTreeInfo(TreeInfo **node_first)
1227 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1231 /* ========================================================================= */
1232 /* some stuff from "files.c" */
1233 /* ========================================================================= */
1235 #if defined(PLATFORM_WIN32)
1237 #define S_IRGRP S_IRUSR
1240 #define S_IROTH S_IRUSR
1243 #define S_IWGRP S_IWUSR
1246 #define S_IWOTH S_IWUSR
1249 #define S_IXGRP S_IXUSR
1252 #define S_IXOTH S_IXUSR
1255 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1260 #endif /* PLATFORM_WIN32 */
1262 /* file permissions for newly written files */
1263 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1264 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1265 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1267 #define MODE_W_PRIVATE (S_IWUSR)
1268 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1269 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1271 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1272 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1274 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1275 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1279 static char *dir = NULL;
1281 #if defined(PLATFORM_WIN32)
1284 dir = checked_malloc(MAX_PATH + 1);
1286 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1289 #elif defined(PLATFORM_UNIX)
1292 if ((dir = getenv("HOME")) == NULL)
1296 if ((pwd = getpwuid(getuid())) != NULL)
1297 dir = getStringCopy(pwd->pw_dir);
1309 char *getCommonDataDir(void)
1311 static char *common_data_dir = NULL;
1313 #if defined(PLATFORM_WIN32)
1314 if (common_data_dir == NULL)
1316 char *dir = checked_malloc(MAX_PATH + 1);
1318 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1319 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1320 common_data_dir = getPath2(dir, program.userdata_subdir);
1322 common_data_dir = options.rw_base_directory;
1325 if (common_data_dir == NULL)
1326 common_data_dir = options.rw_base_directory;
1329 return common_data_dir;
1332 char *getPersonalDataDir(void)
1334 static char *personal_data_dir = NULL;
1336 #if defined(PLATFORM_MACOSX)
1337 if (personal_data_dir == NULL)
1338 personal_data_dir = getPath2(getHomeDir(), "Documents");
1340 if (personal_data_dir == NULL)
1341 personal_data_dir = getHomeDir();
1344 return personal_data_dir;
1347 char *getUserGameDataDir(void)
1349 static char *user_game_data_dir = NULL;
1351 #if defined(PLATFORM_ANDROID)
1352 if (user_game_data_dir == NULL)
1353 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1355 if (user_game_data_dir == NULL)
1356 user_game_data_dir = getPath2(getPersonalDataDir(),
1357 program.userdata_subdir);
1360 return user_game_data_dir;
1363 void updateUserGameDataDir()
1365 #if defined(PLATFORM_MACOSX)
1366 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1367 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1369 /* convert old Unix style game data directory to Mac OS X style, if needed */
1370 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1372 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1374 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1375 userdata_dir_old, userdata_dir_new);
1377 /* continue using Unix style data directory -- this should not happen */
1378 program.userdata_path = getPath2(getPersonalDataDir(),
1379 program.userdata_subdir_unix);
1383 free(userdata_dir_old);
1389 return getUserGameDataDir();
1392 static mode_t posix_umask(mode_t mask)
1394 #if defined(PLATFORM_UNIX)
1401 static int posix_mkdir(const char *pathname, mode_t mode)
1403 #if defined(PLATFORM_WIN32)
1404 return mkdir(pathname);
1406 return mkdir(pathname, mode);
1410 static boolean posix_process_running_setgid()
1412 #if defined(PLATFORM_UNIX)
1413 return (getgid() != getegid());
1419 void createDirectory(char *dir, char *text, int permission_class)
1421 /* leave "other" permissions in umask untouched, but ensure group parts
1422 of USERDATA_DIR_MODE are not masked */
1423 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1424 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1425 mode_t last_umask = posix_umask(0);
1426 mode_t group_umask = ~(dir_mode & S_IRWXG);
1427 int running_setgid = posix_process_running_setgid();
1429 /* if we're setgid, protect files against "other" */
1430 /* else keep umask(0) to make the dir world-writable */
1433 posix_umask(last_umask & group_umask);
1435 dir_mode |= MODE_W_ALL;
1437 if (!directoryExists(dir))
1438 if (posix_mkdir(dir, dir_mode) != 0)
1439 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1440 text, dir, strerror(errno));
1442 if (permission_class == PERMS_PUBLIC && !running_setgid)
1443 chmod(dir, dir_mode);
1445 posix_umask(last_umask); /* restore previous umask */
1448 void InitUserDataDirectory()
1450 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1453 void SetFilePermissions(char *filename, int permission_class)
1455 int running_setgid = posix_process_running_setgid();
1456 int perms = (permission_class == PERMS_PRIVATE ?
1457 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1459 if (permission_class == PERMS_PUBLIC && !running_setgid)
1460 perms |= MODE_W_ALL;
1462 chmod(filename, perms);
1465 char *getCookie(char *file_type)
1467 static char cookie[MAX_COOKIE_LEN + 1];
1469 if (strlen(program.cookie_prefix) + 1 +
1470 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1471 return "[COOKIE ERROR]"; /* should never happen */
1473 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1474 program.cookie_prefix, file_type,
1475 program.version_major, program.version_minor);
1480 int getFileVersionFromCookieString(const char *cookie)
1482 const char *ptr_cookie1, *ptr_cookie2;
1483 const char *pattern1 = "_FILE_VERSION_";
1484 const char *pattern2 = "?.?";
1485 const int len_cookie = strlen(cookie);
1486 const int len_pattern1 = strlen(pattern1);
1487 const int len_pattern2 = strlen(pattern2);
1488 const int len_pattern = len_pattern1 + len_pattern2;
1489 int version_major, version_minor;
1491 if (len_cookie <= len_pattern)
1494 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1495 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1497 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1500 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1501 ptr_cookie2[1] != '.' ||
1502 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1505 version_major = ptr_cookie2[0] - '0';
1506 version_minor = ptr_cookie2[2] - '0';
1508 return VERSION_IDENT(version_major, version_minor, 0, 0);
1511 boolean checkCookieString(const char *cookie, const char *template)
1513 const char *pattern = "_FILE_VERSION_?.?";
1514 const int len_cookie = strlen(cookie);
1515 const int len_template = strlen(template);
1516 const int len_pattern = strlen(pattern);
1518 if (len_cookie != len_template)
1521 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1527 /* ------------------------------------------------------------------------- */
1528 /* setup file list and hash handling functions */
1529 /* ------------------------------------------------------------------------- */
1531 char *getFormattedSetupEntry(char *token, char *value)
1534 static char entry[MAX_LINE_LEN];
1536 /* if value is an empty string, just return token without value */
1540 /* start with the token and some spaces to format output line */
1541 sprintf(entry, "%s:", token);
1542 for (i = strlen(entry); i < token_value_position; i++)
1545 /* continue with the token's value */
1546 strcat(entry, value);
1551 SetupFileList *newSetupFileList(char *token, char *value)
1553 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1555 new->token = getStringCopy(token);
1556 new->value = getStringCopy(value);
1563 void freeSetupFileList(SetupFileList *list)
1568 checked_free(list->token);
1569 checked_free(list->value);
1572 freeSetupFileList(list->next);
1577 char *getListEntry(SetupFileList *list, char *token)
1582 if (strEqual(list->token, token))
1585 return getListEntry(list->next, token);
1588 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1593 if (strEqual(list->token, token))
1595 checked_free(list->value);
1597 list->value = getStringCopy(value);
1601 else if (list->next == NULL)
1602 return (list->next = newSetupFileList(token, value));
1604 return setListEntry(list->next, token, value);
1607 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1612 if (list->next == NULL)
1613 return (list->next = newSetupFileList(token, value));
1615 return addListEntry(list->next, token, value);
1620 static void printSetupFileList(SetupFileList *list)
1625 printf("token: '%s'\n", list->token);
1626 printf("value: '%s'\n", list->value);
1628 printSetupFileList(list->next);
1634 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1635 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1636 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1637 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1639 #define insert_hash_entry hashtable_insert
1640 #define search_hash_entry hashtable_search
1641 #define change_hash_entry hashtable_change
1642 #define remove_hash_entry hashtable_remove
1645 unsigned int get_hash_from_key(void *key)
1650 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1651 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1652 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1653 it works better than many other constants, prime or not) has never been
1654 adequately explained.
1656 If you just want to have a good hash function, and cannot wait, djb2
1657 is one of the best string hash functions i know. It has excellent
1658 distribution and speed on many different sets of keys and table sizes.
1659 You are not likely to do better with one of the "well known" functions
1660 such as PJW, K&R, etc.
1662 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1665 char *str = (char *)key;
1666 unsigned int hash = 5381;
1669 while ((c = *str++))
1670 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1675 static int keys_are_equal(void *key1, void *key2)
1677 return (strEqual((char *)key1, (char *)key2));
1680 SetupFileHash *newSetupFileHash()
1682 SetupFileHash *new_hash =
1683 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1685 if (new_hash == NULL)
1686 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1691 void freeSetupFileHash(SetupFileHash *hash)
1696 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1699 char *getHashEntry(SetupFileHash *hash, char *token)
1704 return search_hash_entry(hash, token);
1707 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1714 value_copy = getStringCopy(value);
1716 /* change value; if it does not exist, insert it as new */
1717 if (!change_hash_entry(hash, token, value_copy))
1718 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1719 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1722 char *removeHashEntry(SetupFileHash *hash, char *token)
1727 return remove_hash_entry(hash, token);
1731 static void printSetupFileHash(SetupFileHash *hash)
1733 BEGIN_HASH_ITERATION(hash, itr)
1735 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1736 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1738 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++)
1807 /* first look for an explicit token/value separator, like ':' or '=' */
1808 if (*line_ptr == ':' || *line_ptr == '=')
1810 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1813 *line_ptr = '\0'; /* terminate token string */
1814 value = line_ptr + 1; /* set beginning of value */
1816 token_value_separator_found = TRUE;
1822 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1823 /* fallback: if no token/value separator found, also allow whitespaces */
1824 if (!token_value_separator_found && !separator_required)
1826 for (line_ptr = token; *line_ptr; line_ptr++)
1828 if (*line_ptr == ' ' || *line_ptr == '\t')
1830 *line_ptr = '\0'; /* terminate token string */
1831 value = line_ptr + 1; /* set beginning of value */
1833 token_value_separator_found = TRUE;
1839 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1840 if (token_value_separator_found)
1842 if (!token_value_separator_warning)
1844 Error(ERR_INFO_LINE, "-");
1846 if (filename != NULL)
1848 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1849 Error(ERR_INFO, "- config file: '%s'", filename);
1853 Error(ERR_WARN, "missing token/value separator(s):");
1856 token_value_separator_warning = TRUE;
1859 if (filename != NULL)
1860 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1862 Error(ERR_INFO, "- line: '%s'", line_raw);
1868 /* cut trailing whitespaces from token */
1869 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1870 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1873 /* cut leading whitespaces from value */
1874 for (; *value; value++)
1875 if (*value != ' ' && *value != '\t')
1880 value = "true"; /* treat tokens without value as "true" */
1889 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1891 /* while the internal (old) interface does not require a token/value
1892 separator (for downwards compatibility with existing files which
1893 don't use them), it is mandatory for the external (new) interface */
1895 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1901 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1902 boolean top_recursion_level, boolean is_hash)
1904 static SetupFileHash *include_filename_hash = NULL;
1905 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1906 char *token, *value, *line_ptr;
1907 void *insert_ptr = NULL;
1908 boolean read_continued_line = FALSE;
1910 int line_nr = 0, token_count = 0, include_count = 0;
1912 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1913 token_value_separator_warning = FALSE;
1916 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1917 token_already_exists_warning = FALSE;
1921 Error(ERR_INFO, "===== opening file: '%s'", filename);
1924 if (!(file = openFile(filename, MODE_READ)))
1926 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1932 Error(ERR_INFO, "===== reading file: '%s'", filename);
1935 /* use "insert pointer" to store list end for constant insertion complexity */
1937 insert_ptr = setup_file_data;
1939 /* on top invocation, create hash to mark included files (to prevent loops) */
1940 if (top_recursion_level)
1941 include_filename_hash = newSetupFileHash();
1943 /* mark this file as already included (to prevent including it again) */
1944 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1946 while (!checkEndOfFile(file))
1948 /* read next line of input file */
1949 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1953 Error(ERR_INFO, "got line: '%s'", line);
1956 /* check if line was completely read and is terminated by line break */
1957 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1960 /* cut trailing line break (this can be newline and/or carriage return) */
1961 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1962 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1965 /* copy raw input line for later use (mainly debugging output) */
1966 strcpy(line_raw, line);
1968 if (read_continued_line)
1971 /* !!! ??? WHY ??? !!! */
1972 /* cut leading whitespaces from input line */
1973 for (line_ptr = line; *line_ptr; line_ptr++)
1974 if (*line_ptr != ' ' && *line_ptr != '\t')
1978 /* append new line to existing line, if there is enough space */
1979 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1980 strcat(previous_line, line_ptr);
1982 strcpy(line, previous_line); /* copy storage buffer to line */
1984 read_continued_line = FALSE;
1987 /* if the last character is '\', continue at next line */
1988 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1990 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1991 strcpy(previous_line, line); /* copy line to storage buffer */
1993 read_continued_line = TRUE;
1998 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1999 line_raw, line_nr, FALSE))
2004 if (strEqual(token, "include"))
2006 if (getHashEntry(include_filename_hash, value) == NULL)
2008 char *basepath = getBasePath(filename);
2009 char *basename = getBaseName(value);
2010 char *filename_include = getPath2(basepath, basename);
2013 Error(ERR_INFO, "[including file '%s']", filename_include);
2016 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2020 free(filename_include);
2026 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2033 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2035 getHashEntry((SetupFileHash *)setup_file_data, token);
2037 if (old_value != NULL)
2039 if (!token_already_exists_warning)
2041 Error(ERR_INFO_LINE, "-");
2042 Error(ERR_WARN, "duplicate token(s) found in config file:");
2043 Error(ERR_INFO, "- config file: '%s'", filename);
2045 token_already_exists_warning = TRUE;
2048 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2049 Error(ERR_INFO, " old value: '%s'", old_value);
2050 Error(ERR_INFO, " new value: '%s'", value);
2054 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2058 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2068 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2069 if (token_value_separator_warning)
2070 Error(ERR_INFO_LINE, "-");
2073 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2074 if (token_already_exists_warning)
2075 Error(ERR_INFO_LINE, "-");
2078 if (token_count == 0 && include_count == 0)
2079 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2081 if (top_recursion_level)
2082 freeSetupFileHash(include_filename_hash);
2089 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2090 boolean top_recursion_level, boolean is_hash)
2092 static SetupFileHash *include_filename_hash = NULL;
2093 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2094 char *token, *value, *line_ptr;
2095 void *insert_ptr = NULL;
2096 boolean read_continued_line = FALSE;
2098 int line_nr = 0, token_count = 0, include_count = 0;
2100 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2101 token_value_separator_warning = FALSE;
2104 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2105 token_already_exists_warning = FALSE;
2108 if (!(file = fopen(filename, MODE_READ)))
2110 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2115 /* use "insert pointer" to store list end for constant insertion complexity */
2117 insert_ptr = setup_file_data;
2119 /* on top invocation, create hash to mark included files (to prevent loops) */
2120 if (top_recursion_level)
2121 include_filename_hash = newSetupFileHash();
2123 /* mark this file as already included (to prevent including it again) */
2124 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2128 /* read next line of input file */
2129 if (!fgets(line, MAX_LINE_LEN, file))
2132 /* check if line was completely read and is terminated by line break */
2133 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2136 /* cut trailing line break (this can be newline and/or carriage return) */
2137 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2138 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2141 /* copy raw input line for later use (mainly debugging output) */
2142 strcpy(line_raw, line);
2144 if (read_continued_line)
2147 /* !!! ??? WHY ??? !!! */
2148 /* cut leading whitespaces from input line */
2149 for (line_ptr = line; *line_ptr; line_ptr++)
2150 if (*line_ptr != ' ' && *line_ptr != '\t')
2154 /* append new line to existing line, if there is enough space */
2155 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2156 strcat(previous_line, line_ptr);
2158 strcpy(line, previous_line); /* copy storage buffer to line */
2160 read_continued_line = FALSE;
2163 /* if the last character is '\', continue at next line */
2164 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2166 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2167 strcpy(previous_line, line); /* copy line to storage buffer */
2169 read_continued_line = TRUE;
2174 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2175 line_raw, line_nr, FALSE))
2180 if (strEqual(token, "include"))
2182 if (getHashEntry(include_filename_hash, value) == NULL)
2184 char *basepath = getBasePath(filename);
2185 char *basename = getBaseName(value);
2186 char *filename_include = getPath2(basepath, basename);
2189 Error(ERR_INFO, "[including file '%s']", filename_include);
2192 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2196 free(filename_include);
2202 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2209 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2211 getHashEntry((SetupFileHash *)setup_file_data, token);
2213 if (old_value != NULL)
2215 if (!token_already_exists_warning)
2217 Error(ERR_INFO_LINE, "-");
2218 Error(ERR_WARN, "duplicate token(s) found in config file:");
2219 Error(ERR_INFO, "- config file: '%s'", filename);
2221 token_already_exists_warning = TRUE;
2224 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2225 Error(ERR_INFO, " old value: '%s'", old_value);
2226 Error(ERR_INFO, " new value: '%s'", value);
2230 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2234 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2244 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2245 if (token_value_separator_warning)
2246 Error(ERR_INFO_LINE, "-");
2249 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2250 if (token_already_exists_warning)
2251 Error(ERR_INFO_LINE, "-");
2254 if (token_count == 0 && include_count == 0)
2255 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2257 if (top_recursion_level)
2258 freeSetupFileHash(include_filename_hash);
2267 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2268 boolean top_recursion_level, boolean is_hash)
2270 static SetupFileHash *include_filename_hash = NULL;
2271 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2272 char *token, *value, *line_ptr;
2273 void *insert_ptr = NULL;
2274 boolean read_continued_line = FALSE;
2277 int token_count = 0;
2279 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2280 token_value_separator_warning = FALSE;
2283 if (!(file = fopen(filename, MODE_READ)))
2285 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2290 /* use "insert pointer" to store list end for constant insertion complexity */
2292 insert_ptr = setup_file_data;
2294 /* on top invocation, create hash to mark included files (to prevent loops) */
2295 if (top_recursion_level)
2296 include_filename_hash = newSetupFileHash();
2298 /* mark this file as already included (to prevent including it again) */
2299 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2303 /* read next line of input file */
2304 if (!fgets(line, MAX_LINE_LEN, file))
2307 /* check if line was completely read and is terminated by line break */
2308 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2311 /* cut trailing line break (this can be newline and/or carriage return) */
2312 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2313 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2316 /* copy raw input line for later use (mainly debugging output) */
2317 strcpy(line_raw, line);
2319 if (read_continued_line)
2321 /* cut leading whitespaces from input line */
2322 for (line_ptr = line; *line_ptr; line_ptr++)
2323 if (*line_ptr != ' ' && *line_ptr != '\t')
2326 /* append new line to existing line, if there is enough space */
2327 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2328 strcat(previous_line, line_ptr);
2330 strcpy(line, previous_line); /* copy storage buffer to line */
2332 read_continued_line = FALSE;
2335 /* if the last character is '\', continue at next line */
2336 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2338 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2339 strcpy(previous_line, line); /* copy line to storage buffer */
2341 read_continued_line = TRUE;
2346 /* cut trailing comment from input line */
2347 for (line_ptr = line; *line_ptr; line_ptr++)
2349 if (*line_ptr == '#')
2356 /* cut trailing whitespaces from input line */
2357 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2358 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2361 /* ignore empty lines */
2365 /* cut leading whitespaces from token */
2366 for (token = line; *token; token++)
2367 if (*token != ' ' && *token != '\t')
2370 /* start with empty value as reliable default */
2373 token_value_separator_found = FALSE;
2375 /* find end of token to determine start of value */
2376 for (line_ptr = token; *line_ptr; line_ptr++)
2379 /* first look for an explicit token/value separator, like ':' or '=' */
2380 if (*line_ptr == ':' || *line_ptr == '=')
2382 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2385 *line_ptr = '\0'; /* terminate token string */
2386 value = line_ptr + 1; /* set beginning of value */
2388 token_value_separator_found = TRUE;
2394 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2395 /* fallback: if no token/value separator found, also allow whitespaces */
2396 if (!token_value_separator_found)
2398 for (line_ptr = token; *line_ptr; line_ptr++)
2400 if (*line_ptr == ' ' || *line_ptr == '\t')
2402 *line_ptr = '\0'; /* terminate token string */
2403 value = line_ptr + 1; /* set beginning of value */
2405 token_value_separator_found = TRUE;
2411 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2412 if (token_value_separator_found)
2414 if (!token_value_separator_warning)
2416 Error(ERR_INFO_LINE, "-");
2417 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2418 Error(ERR_INFO, "- config file: '%s'", filename);
2420 token_value_separator_warning = TRUE;
2423 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2429 /* cut trailing whitespaces from token */
2430 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2431 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2434 /* cut leading whitespaces from value */
2435 for (; *value; value++)
2436 if (*value != ' ' && *value != '\t')
2441 value = "true"; /* treat tokens without value as "true" */
2446 if (strEqual(token, "include"))
2448 if (getHashEntry(include_filename_hash, value) == NULL)
2450 char *basepath = getBasePath(filename);
2451 char *basename = getBaseName(value);
2452 char *filename_include = getPath2(basepath, basename);
2455 Error(ERR_INFO, "[including file '%s']", filename_include);
2458 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2462 free(filename_include);
2466 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2472 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2474 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2483 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2484 if (token_value_separator_warning)
2485 Error(ERR_INFO_LINE, "-");
2488 if (token_count == 0)
2489 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2491 if (top_recursion_level)
2492 freeSetupFileHash(include_filename_hash);
2498 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2502 if (!(file = fopen(filename, MODE_WRITE)))
2504 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2509 BEGIN_HASH_ITERATION(hash, itr)
2511 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2512 HASH_ITERATION_VALUE(itr)));
2514 END_HASH_ITERATION(hash, itr)
2519 SetupFileList *loadSetupFileList(char *filename)
2521 SetupFileList *setup_file_list = newSetupFileList("", "");
2522 SetupFileList *first_valid_list_entry;
2524 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2526 freeSetupFileList(setup_file_list);
2531 first_valid_list_entry = setup_file_list->next;
2533 /* free empty list header */
2534 setup_file_list->next = NULL;
2535 freeSetupFileList(setup_file_list);
2537 return first_valid_list_entry;
2540 SetupFileHash *loadSetupFileHash(char *filename)
2542 SetupFileHash *setup_file_hash = newSetupFileHash();
2544 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2546 freeSetupFileHash(setup_file_hash);
2551 return setup_file_hash;
2554 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2555 char *filename, char *identifier)
2557 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2560 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2561 else if (!checkCookieString(value, identifier))
2562 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2566 /* ========================================================================= */
2567 /* setup file stuff */
2568 /* ========================================================================= */
2570 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2571 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2572 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2574 /* level directory info */
2575 #define LEVELINFO_TOKEN_IDENTIFIER 0
2576 #define LEVELINFO_TOKEN_NAME 1
2577 #define LEVELINFO_TOKEN_NAME_SORTING 2
2578 #define LEVELINFO_TOKEN_AUTHOR 3
2579 #define LEVELINFO_TOKEN_YEAR 4
2580 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2581 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2582 #define LEVELINFO_TOKEN_TESTED_BY 7
2583 #define LEVELINFO_TOKEN_LEVELS 8
2584 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2585 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2586 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2587 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2588 #define LEVELINFO_TOKEN_READONLY 13
2589 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2590 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2591 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2592 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2593 #define LEVELINFO_TOKEN_MUSIC_SET 18
2594 #define LEVELINFO_TOKEN_FILENAME 19
2595 #define LEVELINFO_TOKEN_FILETYPE 20
2596 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2597 #define LEVELINFO_TOKEN_HANDICAP 22
2598 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2600 #define NUM_LEVELINFO_TOKENS 24
2602 static LevelDirTree ldi;
2604 static struct TokenInfo levelinfo_tokens[] =
2606 /* level directory info */
2607 { TYPE_STRING, &ldi.identifier, "identifier" },
2608 { TYPE_STRING, &ldi.name, "name" },
2609 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2610 { TYPE_STRING, &ldi.author, "author" },
2611 { TYPE_STRING, &ldi.year, "year" },
2612 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2613 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2614 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2615 { TYPE_INTEGER, &ldi.levels, "levels" },
2616 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2617 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2618 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2619 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2620 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2621 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2622 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2623 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2624 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2625 { TYPE_STRING, &ldi.music_set, "music_set" },
2626 { TYPE_STRING, &ldi.level_filename, "filename" },
2627 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2628 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2629 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2630 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2633 static struct TokenInfo artworkinfo_tokens[] =
2635 /* artwork directory info */
2636 { TYPE_STRING, &ldi.identifier, "identifier" },
2637 { TYPE_STRING, &ldi.subdir, "subdir" },
2638 { TYPE_STRING, &ldi.name, "name" },
2639 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2640 { TYPE_STRING, &ldi.author, "author" },
2641 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2642 { TYPE_STRING, &ldi.basepath, "basepath" },
2643 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2644 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2645 { TYPE_INTEGER, &ldi.color, "color" },
2646 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2651 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2655 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2656 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2657 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2658 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2661 ti->node_parent = NULL;
2662 ti->node_group = NULL;
2669 ti->fullpath = NULL;
2670 ti->basepath = NULL;
2671 ti->identifier = NULL;
2672 ti->name = getStringCopy(ANONYMOUS_NAME);
2673 ti->name_sorting = NULL;
2674 ti->author = getStringCopy(ANONYMOUS_NAME);
2677 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2678 ti->latest_engine = FALSE; /* default: get from level */
2679 ti->parent_link = FALSE;
2680 ti->in_user_dir = FALSE;
2681 ti->user_defined = FALSE;
2683 ti->class_desc = NULL;
2685 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2687 if (ti->type == TREE_TYPE_LEVEL_DIR)
2689 ti->imported_from = NULL;
2690 ti->imported_by = NULL;
2691 ti->tested_by = NULL;
2693 ti->graphics_set_ecs = NULL;
2694 ti->graphics_set_aga = NULL;
2695 ti->graphics_set = NULL;
2696 ti->sounds_set = NULL;
2697 ti->music_set = NULL;
2698 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2699 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2700 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2702 ti->level_filename = NULL;
2703 ti->level_filetype = NULL;
2705 ti->special_flags = NULL;
2708 ti->first_level = 0;
2710 ti->level_group = FALSE;
2711 ti->handicap_level = 0;
2712 ti->readonly = TRUE;
2713 ti->handicap = TRUE;
2714 ti->skip_levels = FALSE;
2718 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2722 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2724 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2729 /* copy all values from the parent structure */
2731 ti->type = parent->type;
2733 ti->node_top = parent->node_top;
2734 ti->node_parent = parent;
2735 ti->node_group = NULL;
2742 ti->fullpath = NULL;
2743 ti->basepath = NULL;
2744 ti->identifier = NULL;
2745 ti->name = getStringCopy(ANONYMOUS_NAME);
2746 ti->name_sorting = NULL;
2747 ti->author = getStringCopy(parent->author);
2748 ti->year = getStringCopy(parent->year);
2750 ti->sort_priority = parent->sort_priority;
2751 ti->latest_engine = parent->latest_engine;
2752 ti->parent_link = FALSE;
2753 ti->in_user_dir = parent->in_user_dir;
2754 ti->user_defined = parent->user_defined;
2755 ti->color = parent->color;
2756 ti->class_desc = getStringCopy(parent->class_desc);
2758 ti->infotext = getStringCopy(parent->infotext);
2760 if (ti->type == TREE_TYPE_LEVEL_DIR)
2762 ti->imported_from = getStringCopy(parent->imported_from);
2763 ti->imported_by = getStringCopy(parent->imported_by);
2764 ti->tested_by = getStringCopy(parent->tested_by);
2766 ti->graphics_set_ecs = NULL;
2767 ti->graphics_set_aga = NULL;
2768 ti->graphics_set = NULL;
2769 ti->sounds_set = NULL;
2770 ti->music_set = NULL;
2771 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2772 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2773 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2775 ti->level_filename = NULL;
2776 ti->level_filetype = NULL;
2778 ti->special_flags = getStringCopy(parent->special_flags);
2781 ti->first_level = 0;
2783 ti->level_group = FALSE;
2784 ti->handicap_level = 0;
2786 ti->readonly = parent->readonly;
2788 ti->readonly = TRUE;
2790 ti->handicap = TRUE;
2791 ti->skip_levels = FALSE;
2795 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2797 TreeInfo *ti_copy = newTreeInfo();
2799 /* copy all values from the original structure */
2801 ti_copy->type = ti->type;
2803 ti_copy->node_top = ti->node_top;
2804 ti_copy->node_parent = ti->node_parent;
2805 ti_copy->node_group = ti->node_group;
2806 ti_copy->next = ti->next;
2808 ti_copy->cl_first = ti->cl_first;
2809 ti_copy->cl_cursor = ti->cl_cursor;
2811 ti_copy->subdir = getStringCopy(ti->subdir);
2812 ti_copy->fullpath = getStringCopy(ti->fullpath);
2813 ti_copy->basepath = getStringCopy(ti->basepath);
2814 ti_copy->identifier = getStringCopy(ti->identifier);
2815 ti_copy->name = getStringCopy(ti->name);
2816 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2817 ti_copy->author = getStringCopy(ti->author);
2818 ti_copy->year = getStringCopy(ti->year);
2819 ti_copy->imported_from = getStringCopy(ti->imported_from);
2820 ti_copy->imported_by = getStringCopy(ti->imported_by);
2821 ti_copy->tested_by = getStringCopy(ti->tested_by);
2823 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2824 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2825 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2826 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2827 ti_copy->music_set = getStringCopy(ti->music_set);
2828 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2829 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2830 ti_copy->music_path = getStringCopy(ti->music_path);
2832 ti_copy->level_filename = getStringCopy(ti->level_filename);
2833 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2835 ti_copy->special_flags = getStringCopy(ti->special_flags);
2837 ti_copy->levels = ti->levels;
2838 ti_copy->first_level = ti->first_level;
2839 ti_copy->last_level = ti->last_level;
2840 ti_copy->sort_priority = ti->sort_priority;
2842 ti_copy->latest_engine = ti->latest_engine;
2844 ti_copy->level_group = ti->level_group;
2845 ti_copy->parent_link = ti->parent_link;
2846 ti_copy->in_user_dir = ti->in_user_dir;
2847 ti_copy->user_defined = ti->user_defined;
2848 ti_copy->readonly = ti->readonly;
2849 ti_copy->handicap = ti->handicap;
2850 ti_copy->skip_levels = ti->skip_levels;
2852 ti_copy->color = ti->color;
2853 ti_copy->class_desc = getStringCopy(ti->class_desc);
2854 ti_copy->handicap_level = ti->handicap_level;
2856 ti_copy->infotext = getStringCopy(ti->infotext);
2861 void freeTreeInfo(TreeInfo *ti)
2866 checked_free(ti->subdir);
2867 checked_free(ti->fullpath);
2868 checked_free(ti->basepath);
2869 checked_free(ti->identifier);
2871 checked_free(ti->name);
2872 checked_free(ti->name_sorting);
2873 checked_free(ti->author);
2874 checked_free(ti->year);
2876 checked_free(ti->class_desc);
2878 checked_free(ti->infotext);
2880 if (ti->type == TREE_TYPE_LEVEL_DIR)
2882 checked_free(ti->imported_from);
2883 checked_free(ti->imported_by);
2884 checked_free(ti->tested_by);
2886 checked_free(ti->graphics_set_ecs);
2887 checked_free(ti->graphics_set_aga);
2888 checked_free(ti->graphics_set);
2889 checked_free(ti->sounds_set);
2890 checked_free(ti->music_set);
2892 checked_free(ti->graphics_path);
2893 checked_free(ti->sounds_path);
2894 checked_free(ti->music_path);
2896 checked_free(ti->level_filename);
2897 checked_free(ti->level_filetype);
2899 checked_free(ti->special_flags);
2902 // recursively free child node
2904 freeTreeInfo(ti->node_group);
2906 // recursively free next node
2908 freeTreeInfo(ti->next);
2913 void setSetupInfo(struct TokenInfo *token_info,
2914 int token_nr, char *token_value)
2916 int token_type = token_info[token_nr].type;
2917 void *setup_value = token_info[token_nr].value;
2919 if (token_value == NULL)
2922 /* set setup field to corresponding token value */
2927 *(boolean *)setup_value = get_boolean_from_string(token_value);
2931 *(int *)setup_value = get_switch3_from_string(token_value);
2935 *(Key *)setup_value = getKeyFromKeyName(token_value);
2939 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2943 *(int *)setup_value = get_integer_from_string(token_value);
2947 checked_free(*(char **)setup_value);
2948 *(char **)setup_value = getStringCopy(token_value);
2956 static int compareTreeInfoEntries(const void *object1, const void *object2)
2958 const TreeInfo *entry1 = *((TreeInfo **)object1);
2959 const TreeInfo *entry2 = *((TreeInfo **)object2);
2960 int class_sorting1 = 0, class_sorting2 = 0;
2963 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2965 class_sorting1 = LEVELSORTING(entry1);
2966 class_sorting2 = LEVELSORTING(entry2);
2968 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2969 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2970 entry1->type == TREE_TYPE_MUSIC_DIR)
2972 class_sorting1 = ARTWORKSORTING(entry1);
2973 class_sorting2 = ARTWORKSORTING(entry2);
2976 if (entry1->parent_link || entry2->parent_link)
2977 compare_result = (entry1->parent_link ? -1 : +1);
2978 else if (entry1->sort_priority == entry2->sort_priority)
2980 char *name1 = getStringToLower(entry1->name_sorting);
2981 char *name2 = getStringToLower(entry2->name_sorting);
2983 compare_result = strcmp(name1, name2);
2988 else if (class_sorting1 == class_sorting2)
2989 compare_result = entry1->sort_priority - entry2->sort_priority;
2991 compare_result = class_sorting1 - class_sorting2;
2993 return compare_result;
2996 static void createParentTreeInfoNode(TreeInfo *node_parent)
3000 if (node_parent == NULL)
3003 ti_new = newTreeInfo();
3004 setTreeInfoToDefaults(ti_new, node_parent->type);
3006 ti_new->node_parent = node_parent;
3007 ti_new->parent_link = TRUE;
3009 setString(&ti_new->identifier, node_parent->identifier);
3010 setString(&ti_new->name, ".. (parent directory)");
3011 setString(&ti_new->name_sorting, ti_new->name);
3013 setString(&ti_new->subdir, "..");
3014 setString(&ti_new->fullpath, node_parent->fullpath);
3016 ti_new->sort_priority = node_parent->sort_priority;
3017 ti_new->latest_engine = node_parent->latest_engine;
3019 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3021 pushTreeInfo(&node_parent->node_group, ti_new);
3025 /* -------------------------------------------------------------------------- */
3026 /* functions for handling level and custom artwork info cache */
3027 /* -------------------------------------------------------------------------- */
3029 static void LoadArtworkInfoCache()
3031 InitCacheDirectory();
3033 if (artworkinfo_cache_old == NULL)
3035 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3037 /* try to load artwork info hash from already existing cache file */
3038 artworkinfo_cache_old = loadSetupFileHash(filename);
3040 /* if no artwork info cache file was found, start with empty hash */
3041 if (artworkinfo_cache_old == NULL)
3042 artworkinfo_cache_old = newSetupFileHash();
3047 if (artworkinfo_cache_new == NULL)
3048 artworkinfo_cache_new = newSetupFileHash();
3051 static void SaveArtworkInfoCache()
3053 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3055 InitCacheDirectory();
3057 saveSetupFileHash(artworkinfo_cache_new, filename);
3062 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3064 static char *prefix = NULL;
3066 checked_free(prefix);
3068 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3073 /* (identical to above function, but separate string buffer needed -- nasty) */
3074 static char *getCacheToken(char *prefix, char *suffix)
3076 static char *token = NULL;
3078 checked_free(token);
3080 token = getStringCat2WithSeparator(prefix, suffix, ".");
3085 static char *getFileTimestampString(char *filename)
3088 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3090 struct stat file_status;
3092 if (stat(filename, &file_status) != 0) /* cannot stat file */
3093 return getStringCopy(i_to_a(0));
3095 return getStringCopy(i_to_a(file_status.st_mtime));
3099 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3101 struct stat file_status;
3103 if (timestamp_string == NULL)
3106 if (stat(filename, &file_status) != 0) /* cannot stat file */
3109 return (file_status.st_mtime != atoi(timestamp_string));
3112 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3114 char *identifier = level_node->subdir;
3115 char *type_string = ARTWORK_DIRECTORY(type);
3116 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3117 char *token_main = getCacheToken(token_prefix, "CACHED");
3118 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3119 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3120 TreeInfo *artwork_info = NULL;
3122 if (!use_artworkinfo_cache)
3129 artwork_info = newTreeInfo();
3130 setTreeInfoToDefaults(artwork_info, type);
3132 /* set all structure fields according to the token/value pairs */
3133 ldi = *artwork_info;
3134 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3136 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3137 char *value = getHashEntry(artworkinfo_cache_old, token);
3139 setSetupInfo(artworkinfo_tokens, i, value);
3141 /* check if cache entry for this item is invalid or incomplete */
3145 Error(ERR_WARN, "cache entry '%s' invalid", token);
3152 *artwork_info = ldi;
3157 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3158 LEVELINFO_FILENAME);
3159 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3160 ARTWORKINFO_FILENAME(type));
3162 /* check if corresponding "levelinfo.conf" file has changed */
3163 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3164 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3166 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3169 /* check if corresponding "<artworkinfo>.conf" file has changed */
3170 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3171 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3173 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3178 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
3181 checked_free(filename_levelinfo);
3182 checked_free(filename_artworkinfo);
3185 if (!cached && artwork_info != NULL)
3187 freeTreeInfo(artwork_info);
3192 return artwork_info;
3195 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3196 LevelDirTree *level_node, int type)
3198 char *identifier = level_node->subdir;
3199 char *type_string = ARTWORK_DIRECTORY(type);
3200 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3201 char *token_main = getCacheToken(token_prefix, "CACHED");
3202 boolean set_cache_timestamps = TRUE;
3205 setHashEntry(artworkinfo_cache_new, token_main, "true");
3207 if (set_cache_timestamps)
3209 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3210 LEVELINFO_FILENAME);
3211 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3212 ARTWORKINFO_FILENAME(type));
3213 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3214 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3216 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3217 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3219 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3220 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3222 checked_free(filename_levelinfo);
3223 checked_free(filename_artworkinfo);
3224 checked_free(timestamp_levelinfo);
3225 checked_free(timestamp_artworkinfo);
3228 ldi = *artwork_info;
3229 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3231 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3232 char *value = getSetupValue(artworkinfo_tokens[i].type,
3233 artworkinfo_tokens[i].value);
3235 setHashEntry(artworkinfo_cache_new, token, value);
3240 /* -------------------------------------------------------------------------- */
3241 /* functions for loading level info and custom artwork info */
3242 /* -------------------------------------------------------------------------- */
3244 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3245 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3247 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3248 TreeInfo *node_parent,
3249 char *level_directory,
3250 char *directory_name)
3253 static unsigned int progress_delay = 0;
3254 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3256 char *directory_path = getPath2(level_directory, directory_name);
3257 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3258 SetupFileHash *setup_file_hash;
3259 LevelDirTree *leveldir_new = NULL;
3262 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3263 if (!options.debug && !fileExists(filename))
3265 free(directory_path);
3271 setup_file_hash = loadSetupFileHash(filename);
3273 if (setup_file_hash == NULL)
3275 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3277 free(directory_path);
3283 leveldir_new = newTreeInfo();
3286 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3288 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3290 leveldir_new->subdir = getStringCopy(directory_name);
3292 checkSetupFileHashIdentifier(setup_file_hash, filename,
3293 getCookie("LEVELINFO"));
3295 /* set all structure fields according to the token/value pairs */
3296 ldi = *leveldir_new;
3297 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3298 setSetupInfo(levelinfo_tokens, i,
3299 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3300 *leveldir_new = ldi;
3302 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3303 setString(&leveldir_new->name, leveldir_new->subdir);
3305 if (leveldir_new->identifier == NULL)
3306 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3308 if (leveldir_new->name_sorting == NULL)
3309 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3311 if (node_parent == NULL) /* top level group */
3313 leveldir_new->basepath = getStringCopy(level_directory);
3314 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3316 else /* sub level group */
3318 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3319 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3323 if (leveldir_new->levels < 1)
3324 leveldir_new->levels = 1;
3327 leveldir_new->last_level =
3328 leveldir_new->first_level + leveldir_new->levels - 1;
3330 leveldir_new->in_user_dir =
3331 (!strEqual(leveldir_new->basepath, options.level_directory));
3334 printf("::: '%s' -> %d\n",
3335 leveldir_new->identifier,
3336 leveldir_new->in_user_dir);
3339 /* adjust some settings if user's private level directory was detected */
3340 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3341 leveldir_new->in_user_dir &&
3342 (strEqual(leveldir_new->subdir, getLoginName()) ||
3343 strEqual(leveldir_new->name, getLoginName()) ||
3344 strEqual(leveldir_new->author, getRealName())))
3346 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3347 leveldir_new->readonly = FALSE;
3350 leveldir_new->user_defined =
3351 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3353 leveldir_new->color = LEVELCOLOR(leveldir_new);
3355 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3357 leveldir_new->handicap_level = /* set handicap to default value */
3358 (leveldir_new->user_defined || !leveldir_new->handicap ?
3359 leveldir_new->last_level : leveldir_new->first_level);
3363 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3364 leveldir_new->level_group);
3366 if (leveldir_new->level_group ||
3367 DelayReached(&progress_delay, progress_delay_value))
3368 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3371 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3375 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3377 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3379 /* skip level sets without levels (which are probably artwork base sets) */
3381 freeSetupFileHash(setup_file_hash);
3382 free(directory_path);
3390 pushTreeInfo(node_first, leveldir_new);
3392 freeSetupFileHash(setup_file_hash);
3394 if (leveldir_new->level_group)
3396 /* create node to link back to current level directory */
3397 createParentTreeInfoNode(leveldir_new);
3399 /* recursively step into sub-directory and look for more level series */
3400 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3401 leveldir_new, directory_path);
3404 free(directory_path);
3411 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3412 TreeInfo *node_parent,
3413 char *level_directory)
3416 DirectoryEntry *dir_entry;
3417 boolean valid_entry_found = FALSE;
3420 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3423 if ((dir = openDirectory(level_directory)) == NULL)
3425 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3431 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3434 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3436 char *directory_name = dir_entry->basename;
3437 char *directory_path = getPath2(level_directory, directory_name);
3440 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3443 /* skip entries for current and parent directory */
3444 if (strEqual(directory_name, ".") ||
3445 strEqual(directory_name, ".."))
3447 free(directory_path);
3453 /* find out if directory entry is itself a directory */
3454 if (!dir_entry->is_directory) /* not a directory */
3456 free(directory_path);
3459 Error(ERR_INFO, "* entry '%s' is not a directory ...", directory_name);
3465 /* find out if directory entry is itself a directory */
3466 struct stat file_status;
3467 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3468 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3470 free(directory_path);
3476 free(directory_path);
3478 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3479 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3480 strEqual(directory_name, MUSIC_DIRECTORY))
3483 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3488 closeDirectory(dir);
3490 /* special case: top level directory may directly contain "levelinfo.conf" */
3491 if (node_parent == NULL && !valid_entry_found)
3493 /* check if this directory directly contains a file "levelinfo.conf" */
3494 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3495 level_directory, ".");
3498 if (!valid_entry_found)
3499 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3505 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3506 TreeInfo *node_parent,
3507 char *level_directory)
3510 struct dirent *dir_entry;
3511 boolean valid_entry_found = FALSE;
3514 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3517 if ((dir = opendir(level_directory)) == NULL)
3519 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3525 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3528 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3530 struct stat file_status;
3531 char *directory_name = dir_entry->d_name;
3532 char *directory_path = getPath2(level_directory, directory_name);
3535 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3538 /* skip entries for current and parent directory */
3539 if (strEqual(directory_name, ".") ||
3540 strEqual(directory_name, ".."))
3542 free(directory_path);
3546 /* find out if directory entry is itself a directory */
3547 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3548 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3550 free(directory_path);
3554 free(directory_path);
3556 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3557 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3558 strEqual(directory_name, MUSIC_DIRECTORY))
3561 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3568 /* special case: top level directory may directly contain "levelinfo.conf" */
3569 if (node_parent == NULL && !valid_entry_found)
3571 /* check if this directory directly contains a file "levelinfo.conf" */
3572 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3573 level_directory, ".");
3576 if (!valid_entry_found)
3577 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3582 boolean AdjustGraphicsForEMC()
3584 boolean settings_changed = FALSE;
3586 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3587 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3589 return settings_changed;
3592 void LoadLevelInfo()
3594 InitUserLevelDirectory(getLoginName());
3596 DrawInitText("Loading level series", 120, FC_GREEN);
3598 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3599 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3601 /* after loading all level set information, clone the level directory tree
3602 and remove all level sets without levels (these may still contain artwork
3603 to be offered in the setup menu as "custom artwork", and are therefore
3604 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3605 leveldir_first_all = leveldir_first;
3606 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3608 AdjustGraphicsForEMC();
3610 /* before sorting, the first entries will be from the user directory */
3611 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3613 if (leveldir_first == NULL)
3614 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3616 sortTreeInfo(&leveldir_first);
3619 dumpTreeInfo(leveldir_first, 0);
3625 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3626 TreeInfo *node_parent,
3627 char *base_directory,
3628 char *directory_name, int type)
3630 char *directory_path = getPath2(base_directory, directory_name);
3631 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3632 SetupFileHash *setup_file_hash = NULL;
3633 TreeInfo *artwork_new = NULL;
3636 if (fileExists(filename))
3637 setup_file_hash = loadSetupFileHash(filename);
3639 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3642 DirectoryEntry *dir_entry;
3643 boolean valid_file_found = FALSE;
3645 if ((dir = openDirectory(directory_path)) != NULL)
3647 while ((dir_entry = readDirectory(dir)) != NULL)
3649 char *entry_name = dir_entry->basename;
3651 if (FileIsArtworkType(entry_name, type))
3653 valid_file_found = TRUE;
3659 closeDirectory(dir);
3662 if (!valid_file_found)
3664 if (!strEqual(directory_name, "."))
3665 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3667 free(directory_path);
3674 artwork_new = newTreeInfo();
3677 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3679 setTreeInfoToDefaults(artwork_new, type);
3681 artwork_new->subdir = getStringCopy(directory_name);
3683 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3686 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3689 /* set all structure fields according to the token/value pairs */
3691 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3692 setSetupInfo(levelinfo_tokens, i,
3693 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3696 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3697 setString(&artwork_new->name, artwork_new->subdir);
3699 if (artwork_new->identifier == NULL)
3700 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3702 if (artwork_new->name_sorting == NULL)
3703 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3706 if (node_parent == NULL) /* top level group */
3708 artwork_new->basepath = getStringCopy(base_directory);
3709 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3711 else /* sub level group */
3713 artwork_new->basepath = getStringCopy(node_parent->basepath);
3714 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3717 artwork_new->in_user_dir =
3718 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3720 /* (may use ".sort_priority" from "setup_file_hash" above) */
3721 artwork_new->color = ARTWORKCOLOR(artwork_new);
3723 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3725 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3727 if (strEqual(artwork_new->subdir, "."))
3729 if (artwork_new->user_defined)
3731 setString(&artwork_new->identifier, "private");
3732 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3736 setString(&artwork_new->identifier, "classic");
3737 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3740 /* set to new values after changing ".sort_priority" */
3741 artwork_new->color = ARTWORKCOLOR(artwork_new);
3743 setString(&artwork_new->class_desc,
3744 getLevelClassDescription(artwork_new));
3748 setString(&artwork_new->identifier, artwork_new->subdir);
3751 setString(&artwork_new->name, artwork_new->identifier);
3752 setString(&artwork_new->name_sorting, artwork_new->name);
3756 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3759 pushTreeInfo(node_first, artwork_new);
3761 freeSetupFileHash(setup_file_hash);
3763 free(directory_path);
3771 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3772 TreeInfo *node_parent,
3773 char *base_directory,
3774 char *directory_name, int type)
3776 char *directory_path = getPath2(base_directory, directory_name);
3777 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3778 SetupFileHash *setup_file_hash = NULL;
3779 TreeInfo *artwork_new = NULL;
3782 if (fileExists(filename))
3783 setup_file_hash = loadSetupFileHash(filename);
3785 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3788 struct dirent *dir_entry;
3789 boolean valid_file_found = FALSE;
3791 if ((dir = opendir(directory_path)) != NULL)
3793 while ((dir_entry = readdir(dir)) != NULL)
3795 char *entry_name = dir_entry->d_name;
3797 if (FileIsArtworkType(entry_name, type))
3799 valid_file_found = TRUE;
3807 if (!valid_file_found)
3809 if (!strEqual(directory_name, "."))
3810 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3812 free(directory_path);
3819 artwork_new = newTreeInfo();
3822 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3824 setTreeInfoToDefaults(artwork_new, type);
3826 artwork_new->subdir = getStringCopy(directory_name);
3828 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3831 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3834 /* set all structure fields according to the token/value pairs */
3836 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3837 setSetupInfo(levelinfo_tokens, i,
3838 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3841 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3842 setString(&artwork_new->name, artwork_new->subdir);
3844 if (artwork_new->identifier == NULL)
3845 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3847 if (artwork_new->name_sorting == NULL)
3848 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3851 if (node_parent == NULL) /* top level group */
3853 artwork_new->basepath = getStringCopy(base_directory);
3854 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3856 else /* sub level group */
3858 artwork_new->basepath = getStringCopy(node_parent->basepath);
3859 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3862 artwork_new->in_user_dir =
3863 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3865 /* (may use ".sort_priority" from "setup_file_hash" above) */
3866 artwork_new->color = ARTWORKCOLOR(artwork_new);
3868 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3870 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3872 if (strEqual(artwork_new->subdir, "."))
3874 if (artwork_new->user_defined)
3876 setString(&artwork_new->identifier, "private");
3877 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3881 setString(&artwork_new->identifier, "classic");
3882 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3885 /* set to new values after changing ".sort_priority" */
3886 artwork_new->color = ARTWORKCOLOR(artwork_new);
3888 setString(&artwork_new->class_desc,
3889 getLevelClassDescription(artwork_new));
3893 setString(&artwork_new->identifier, artwork_new->subdir);
3896 setString(&artwork_new->name, artwork_new->identifier);
3897 setString(&artwork_new->name_sorting, artwork_new->name);
3901 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3904 pushTreeInfo(node_first, artwork_new);
3906 freeSetupFileHash(setup_file_hash);
3908 free(directory_path);
3918 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3919 TreeInfo *node_parent,
3920 char *base_directory, int type)
3923 DirectoryEntry *dir_entry;
3924 boolean valid_entry_found = FALSE;
3926 if ((dir = openDirectory(base_directory)) == NULL)
3928 /* display error if directory is main "options.graphics_directory" etc. */
3929 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3930 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3935 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3937 char *directory_name = dir_entry->basename;
3938 char *directory_path = getPath2(base_directory, directory_name);
3940 /* skip directory entries for current and parent directory */
3941 if (strEqual(directory_name, ".") ||
3942 strEqual(directory_name, ".."))
3944 free(directory_path);
3950 /* skip directory entries which are not a directory */
3951 if (!dir_entry->is_directory) /* not a directory */
3953 free(directory_path);
3958 /* skip directory entries which are not a directory or are not accessible */
3959 struct stat file_status;
3960 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3961 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3963 free(directory_path);
3969 free(directory_path);
3971 /* check if this directory contains artwork with or without config file */
3972 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3974 directory_name, type);
3977 closeDirectory(dir);
3979 /* check if this directory directly contains artwork itself */
3980 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3981 base_directory, ".",
3983 if (!valid_entry_found)
3984 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3990 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3991 TreeInfo *node_parent,
3992 char *base_directory, int type)
3995 struct dirent *dir_entry;
3996 boolean valid_entry_found = FALSE;
3998 if ((dir = opendir(base_directory)) == NULL)
4000 /* display error if directory is main "options.graphics_directory" etc. */
4001 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4002 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
4007 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
4009 struct stat file_status;
4010 char *directory_name = dir_entry->d_name;
4011 char *directory_path = getPath2(base_directory, directory_name);
4013 /* skip directory entries for current and parent directory */
4014 if (strEqual(directory_name, ".") ||
4015 strEqual(directory_name, ".."))
4017 free(directory_path);
4021 /* skip directory entries which are not a directory or are not accessible */
4022 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
4023 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
4025 free(directory_path);
4029 free(directory_path);
4031 /* check if this directory contains artwork with or without config file */
4032 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4034 directory_name, type);
4039 /* check if this directory directly contains artwork itself */
4040 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4041 base_directory, ".",
4043 if (!valid_entry_found)
4044 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4050 static TreeInfo *getDummyArtworkInfo(int type)
4052 /* this is only needed when there is completely no artwork available */
4053 TreeInfo *artwork_new = newTreeInfo();
4055 setTreeInfoToDefaults(artwork_new, type);
4057 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4058 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4059 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4061 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4062 setString(&artwork_new->name, UNDEFINED_FILENAME);
4063 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4068 void LoadArtworkInfo()
4070 LoadArtworkInfoCache();
4072 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
4074 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4075 options.graphics_directory,
4076 TREE_TYPE_GRAPHICS_DIR);
4077 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4078 getUserGraphicsDir(),
4079 TREE_TYPE_GRAPHICS_DIR);
4081 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4082 options.sounds_directory,
4083 TREE_TYPE_SOUNDS_DIR);
4084 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4086 TREE_TYPE_SOUNDS_DIR);
4088 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4089 options.music_directory,
4090 TREE_TYPE_MUSIC_DIR);
4091 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4093 TREE_TYPE_MUSIC_DIR);
4095 if (artwork.gfx_first == NULL)
4096 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4097 if (artwork.snd_first == NULL)
4098 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4099 if (artwork.mus_first == NULL)
4100 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4102 /* before sorting, the first entries will be from the user directory */
4103 artwork.gfx_current =
4104 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4105 if (artwork.gfx_current == NULL)
4106 artwork.gfx_current =
4107 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4108 if (artwork.gfx_current == NULL)
4109 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4111 artwork.snd_current =
4112 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4113 if (artwork.snd_current == NULL)
4114 artwork.snd_current =
4115 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4116 if (artwork.snd_current == NULL)
4117 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4119 artwork.mus_current =
4120 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4121 if (artwork.mus_current == NULL)
4122 artwork.mus_current =
4123 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4124 if (artwork.mus_current == NULL)
4125 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4127 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4128 artwork.snd_current_identifier = artwork.snd_current->identifier;
4129 artwork.mus_current_identifier = artwork.mus_current->identifier;
4132 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
4133 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
4134 printf("music set == %s\n\n", artwork.mus_current_identifier);
4137 sortTreeInfo(&artwork.gfx_first);
4138 sortTreeInfo(&artwork.snd_first);
4139 sortTreeInfo(&artwork.mus_first);
4142 dumpTreeInfo(artwork.gfx_first, 0);
4143 dumpTreeInfo(artwork.snd_first, 0);
4144 dumpTreeInfo(artwork.mus_first, 0);
4148 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
4149 LevelDirTree *level_node)
4152 static unsigned int progress_delay = 0;
4153 unsigned int progress_delay_value = 100; /* (in milliseconds) */
4155 int type = (*artwork_node)->type;
4157 /* recursively check all level directories for artwork sub-directories */
4161 /* check all tree entries for artwork, but skip parent link entries */
4162 if (!level_node->parent_link)
4164 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4165 boolean cached = (artwork_new != NULL);
4169 pushTreeInfo(artwork_node, artwork_new);
4173 TreeInfo *topnode_last = *artwork_node;
4174 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4175 ARTWORK_DIRECTORY(type));
4177 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4179 if (topnode_last != *artwork_node) /* check for newly added node */
4181 artwork_new = *artwork_node;
4183 setString(&artwork_new->identifier, level_node->subdir);
4184 setString(&artwork_new->name, level_node->name);
4185 setString(&artwork_new->name_sorting, level_node->name_sorting);
4187 artwork_new->sort_priority = level_node->sort_priority;
4188 artwork_new->color = LEVELCOLOR(artwork_new);
4194 /* insert artwork info (from old cache or filesystem) into new cache */
4195 if (artwork_new != NULL)
4196 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4200 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
4201 level_node->level_group);
4203 if (level_node->level_group ||
4204 DelayReached(&progress_delay, progress_delay_value))
4205 DrawInitText(level_node->name, 150, FC_YELLOW);
4208 if (level_node->node_group != NULL)
4209 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
4211 level_node = level_node->next;
4215 void LoadLevelArtworkInfo()
4217 print_timestamp_init("LoadLevelArtworkInfo");
4219 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4221 print_timestamp_time("DrawTimeText");
4223 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
4224 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4225 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
4226 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4227 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
4228 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4230 SaveArtworkInfoCache();
4232 print_timestamp_time("SaveArtworkInfoCache");
4234 /* needed for reloading level artwork not known at ealier stage */
4236 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
4238 artwork.gfx_current =
4239 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4240 if (artwork.gfx_current == NULL)
4241 artwork.gfx_current =
4242 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4243 if (artwork.gfx_current == NULL)
4244 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4247 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
4249 artwork.snd_current =
4250 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4251 if (artwork.snd_current == NULL)
4252 artwork.snd_current =
4253 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4254 if (artwork.snd_current == NULL)
4255 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4258 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
4260 artwork.mus_current =
4261 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4262 if (artwork.mus_current == NULL)
4263 artwork.mus_current =
4264 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4265 if (artwork.mus_current == NULL)
4266 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4269 print_timestamp_time("getTreeInfoFromIdentifier");
4271 sortTreeInfo(&artwork.gfx_first);
4272 sortTreeInfo(&artwork.snd_first);
4273 sortTreeInfo(&artwork.mus_first);
4275 print_timestamp_time("sortTreeInfo");
4278 dumpTreeInfo(artwork.gfx_first, 0);
4279 dumpTreeInfo(artwork.snd_first, 0);
4280 dumpTreeInfo(artwork.mus_first, 0);
4283 print_timestamp_done("LoadLevelArtworkInfo");
4286 static void SaveUserLevelInfo()
4288 LevelDirTree *level_info;
4293 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
4295 if (!(file = fopen(filename, MODE_WRITE)))
4297 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4302 level_info = newTreeInfo();
4304 /* always start with reliable default values */
4305 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4307 setString(&level_info->name, getLoginName());
4308 setString(&level_info->author, getRealName());
4309 level_info->levels = 100;
4310 level_info->first_level = 1;
4312 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4314 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4315 getCookie("LEVELINFO")));
4318 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4320 if (i == LEVELINFO_TOKEN_NAME ||
4321 i == LEVELINFO_TOKEN_AUTHOR ||
4322 i == LEVELINFO_TOKEN_LEVELS ||
4323 i == LEVELINFO_TOKEN_FIRST_LEVEL)
4324 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4326 /* just to make things nicer :) */
4327 if (i == LEVELINFO_TOKEN_AUTHOR)
4328 fprintf(file, "\n");
4331 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4335 SetFilePermissions(filename, PERMS_PRIVATE);
4337 freeTreeInfo(level_info);
4341 char *getSetupValue(int type, void *value)
4343 static char value_string[MAX_LINE_LEN];
4351 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4355 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4359 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4360 *(int *)value == FALSE ? "off" : "on"));
4364 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4367 case TYPE_YES_NO_AUTO:
4368 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4369 *(int *)value == FALSE ? "no" : "yes"));
4373 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4377 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4381 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4385 sprintf(value_string, "%d", *(int *)value);
4389 if (*(char **)value == NULL)
4392 strcpy(value_string, *(char **)value);
4396 value_string[0] = '\0';
4400 if (type & TYPE_GHOSTED)
4401 strcpy(value_string, "n/a");
4403 return value_string;
4406 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4410 static char token_string[MAX_LINE_LEN];
4411 int token_type = token_info[token_nr].type;
4412 void *setup_value = token_info[token_nr].value;
4413 char *token_text = token_info[token_nr].text;
4414 char *value_string = getSetupValue(token_type, setup_value);
4416 /* build complete token string */
4417 sprintf(token_string, "%s%s", prefix, token_text);
4419 /* build setup entry line */
4420 line = getFormattedSetupEntry(token_string, value_string);
4422 if (token_type == TYPE_KEY_X11)
4424 Key key = *(Key *)setup_value;
4425 char *keyname = getKeyNameFromKey(key);
4427 /* add comment, if useful */
4428 if (!strEqual(keyname, "(undefined)") &&
4429 !strEqual(keyname, "(unknown)"))
4431 /* add at least one whitespace */
4433 for (i = strlen(line); i < token_comment_position; i++)
4437 strcat(line, keyname);
4444 void LoadLevelSetup_LastSeries()
4446 /* ----------------------------------------------------------------------- */
4447 /* ~/.<program>/levelsetup.conf */
4448 /* ----------------------------------------------------------------------- */
4450 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4451 SetupFileHash *level_setup_hash = NULL;
4453 /* always start with reliable default values */
4454 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4456 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
4457 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4459 if (leveldir_current == NULL)
4460 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4463 if ((level_setup_hash = loadSetupFileHash(filename)))
4465 char *last_level_series =
4466 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4468 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4470 if (leveldir_current == NULL)
4471 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4473 checkSetupFileHashIdentifier(level_setup_hash, filename,
4474 getCookie("LEVELSETUP"));
4476 freeSetupFileHash(level_setup_hash);
4479 Error(ERR_WARN, "using default setup values");
4484 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4486 /* ----------------------------------------------------------------------- */
4487 /* ~/.<program>/levelsetup.conf */
4488 /* ----------------------------------------------------------------------- */
4490 // check if the current level directory structure is available at this point
4491 if (leveldir_current == NULL)
4494 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4495 char *level_subdir = leveldir_current->subdir;
4498 InitUserDataDirectory();
4500 if (!(file = fopen(filename, MODE_WRITE)))
4502 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4509 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4510 getCookie("LEVELSETUP")));
4512 if (deactivate_last_level_series)
4513 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4515 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4520 SetFilePermissions(filename, PERMS_PRIVATE);
4525 void SaveLevelSetup_LastSeries()
4527 SaveLevelSetup_LastSeries_Ext(FALSE);
4530 void SaveLevelSetup_LastSeries_Deactivate()
4532 SaveLevelSetup_LastSeries_Ext(TRUE);
4537 static void checkSeriesInfo()
4539 static char *level_directory = NULL;
4542 DirectoryEntry *dir_entry;
4545 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4547 level_directory = getPath2((leveldir_current->in_user_dir ?
4548 getUserLevelDir(NULL) :
4549 options.level_directory),
4550 leveldir_current->fullpath);
4552 if ((dir = openDirectory(level_directory)) == NULL)
4554 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4560 while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */
4562 if (strlen(dir_entry->basename) > 4 &&
4563 dir_entry->basename[3] == '.' &&
4564 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4566 char levelnum_str[4];
4569 strncpy(levelnum_str, dir_entry->basename, 3);
4570 levelnum_str[3] = '\0';
4572 levelnum_value = atoi(levelnum_str);
4574 if (levelnum_value < leveldir_current->first_level)
4576 Error(ERR_WARN, "additional level %d found", levelnum_value);
4577 leveldir_current->first_level = levelnum_value;
4579 else if (levelnum_value > leveldir_current->last_level)
4581 Error(ERR_WARN, "additional level %d found", levelnum_value);
4582 leveldir_current->last_level = levelnum_value;
4588 closeDirectory(dir);
4593 static void checkSeriesInfo()
4595 static char *level_directory = NULL;
4598 struct dirent *dir_entry;
4601 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4603 level_directory = getPath2((leveldir_current->in_user_dir ?
4604 getUserLevelDir(NULL) :
4605 options.level_directory),
4606 leveldir_current->fullpath);
4608 if ((dir = opendir(level_directory)) == NULL)
4610 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4616 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4618 if (strlen(dir_entry->d_name) > 4 &&
4619 dir_entry->d_name[3] == '.' &&
4620 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4622 char levelnum_str[4];
4625 strncpy(levelnum_str, dir_entry->d_name, 3);
4626 levelnum_str[3] = '\0';
4628 levelnum_value = atoi(levelnum_str);
4630 if (levelnum_value < leveldir_current->first_level)
4632 Error(ERR_WARN, "additional level %d found", levelnum_value);
4633 leveldir_current->first_level = levelnum_value;
4635 else if (levelnum_value > leveldir_current->last_level)
4637 Error(ERR_WARN, "additional level %d found", levelnum_value);
4638 leveldir_current->last_level = levelnum_value;
4649 void LoadLevelSetup_SeriesInfo()
4652 SetupFileHash *level_setup_hash = NULL;
4653 char *level_subdir = leveldir_current->subdir;
4656 /* always start with reliable default values */
4657 level_nr = leveldir_current->first_level;
4659 for (i = 0; i < MAX_LEVELS; i++)
4661 LevelStats_setPlayed(i, 0);
4662 LevelStats_setSolved(i, 0);
4665 checkSeriesInfo(leveldir_current);
4667 /* ----------------------------------------------------------------------- */
4668 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4669 /* ----------------------------------------------------------------------- */
4671 level_subdir = leveldir_current->subdir;
4673 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4675 if ((level_setup_hash = loadSetupFileHash(filename)))
4679 /* get last played level in this level set */
4681 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4685 level_nr = atoi(token_value);
4687 if (level_nr < leveldir_current->first_level)
4688 level_nr = leveldir_current->first_level;
4689 if (level_nr > leveldir_current->last_level)
4690 level_nr = leveldir_current->last_level;
4693 /* get handicap level in this level set */
4695 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4699 int level_nr = atoi(token_value);
4701 if (level_nr < leveldir_current->first_level)
4702 level_nr = leveldir_current->first_level;
4703 if (level_nr > leveldir_current->last_level + 1)
4704 level_nr = leveldir_current->last_level;
4706 if (leveldir_current->user_defined || !leveldir_current->handicap)
4707 level_nr = leveldir_current->last_level;
4709 leveldir_current->handicap_level = level_nr;
4712 /* get number of played and solved levels in this level set */
4714 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4716 char *token = HASH_ITERATION_TOKEN(itr);
4717 char *value = HASH_ITERATION_VALUE(itr);
4719 if (strlen(token) == 3 &&
4720 token[0] >= '0' && token[0] <= '9' &&
4721 token[1] >= '0' && token[1] <= '9' &&
4722 token[2] >= '0' && token[2] <= '9')
4724 int level_nr = atoi(token);
4727 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4729 value = strchr(value, ' ');
4732 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4735 END_HASH_ITERATION(hash, itr)
4737 checkSetupFileHashIdentifier(level_setup_hash, filename,
4738 getCookie("LEVELSETUP"));
4740 freeSetupFileHash(level_setup_hash);
4743 Error(ERR_WARN, "using default setup values");
4748 void SaveLevelSetup_SeriesInfo()
4751 char *level_subdir = leveldir_current->subdir;
4752 char *level_nr_str = int2str(level_nr, 0);
4753 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4757 /* ----------------------------------------------------------------------- */
4758 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4759 /* ----------------------------------------------------------------------- */
4761 InitLevelSetupDirectory(level_subdir);
4763 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4765 if (!(file = fopen(filename, MODE_WRITE)))
4767 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4772 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4773 getCookie("LEVELSETUP")));
4774 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4776 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4777 handicap_level_str));
4779 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4782 if (LevelStats_getPlayed(i) > 0 ||
4783 LevelStats_getSolved(i) > 0)
4788 sprintf(token, "%03d", i);
4789 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4791 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4797 SetFilePermissions(filename, PERMS_PRIVATE);
4802 int LevelStats_getPlayed(int nr)
4804 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4807 int LevelStats_getSolved(int nr)
4809 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4812 void LevelStats_setPlayed(int nr, int value)
4814 if (nr >= 0 && nr < MAX_LEVELS)
4815 level_stats[nr].played = value;
4818 void LevelStats_setSolved(int nr, int value)
4820 if (nr >= 0 && nr < MAX_LEVELS)
4821 level_stats[nr].solved = value;
4824 void LevelStats_incPlayed(int nr)
4826 if (nr >= 0 && nr < MAX_LEVELS)
4827 level_stats[nr].played++;
4830 void LevelStats_incSolved(int nr)
4832 if (nr >= 0 && nr < MAX_LEVELS)
4833 level_stats[nr].solved++;