1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
33 #define ENABLE_UNUSED_CODE 0 /* currently unused functions */
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 char *data_dir = getCommonDataDir();
137 char *score_subdir = SCORES_DIRECTORY;
139 checked_free(score_dir);
141 if (level_subdir != NULL)
142 score_dir = getPath3(data_dir, score_subdir, level_subdir);
144 score_dir = getPath2(data_dir, score_subdir);
149 static char *getLevelSetupDir(char *level_subdir)
151 static char *levelsetup_dir = NULL;
152 char *data_dir = getUserGameDataDir();
153 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
155 checked_free(levelsetup_dir);
157 if (level_subdir != NULL)
158 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
160 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
162 return levelsetup_dir;
165 static char *getCacheDir()
167 static char *cache_dir = NULL;
169 if (cache_dir == NULL)
170 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
175 static char *getLevelDirFromTreeInfo(TreeInfo *node)
177 static char *level_dir = NULL;
180 return options.level_directory;
182 checked_free(level_dir);
184 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
185 options.level_directory), node->fullpath);
190 char *getCurrentLevelDir()
192 return getLevelDirFromTreeInfo(leveldir_current);
195 static char *getTapeDir(char *level_subdir)
197 static char *tape_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *tape_subdir = TAPES_DIRECTORY;
201 checked_free(tape_dir);
203 if (level_subdir != NULL)
204 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
206 tape_dir = getPath2(data_dir, tape_subdir);
211 static char *getSolutionTapeDir()
213 static char *tape_dir = NULL;
214 char *data_dir = getCurrentLevelDir();
215 char *tape_subdir = TAPES_DIRECTORY;
217 checked_free(tape_dir);
219 tape_dir = getPath2(data_dir, tape_subdir);
224 static char *getDefaultGraphicsDir(char *graphics_subdir)
226 static char *graphics_dir = NULL;
228 if (graphics_subdir == NULL)
229 return options.graphics_directory;
231 checked_free(graphics_dir);
233 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
238 static char *getDefaultSoundsDir(char *sounds_subdir)
240 static char *sounds_dir = NULL;
242 if (sounds_subdir == NULL)
243 return options.sounds_directory;
245 checked_free(sounds_dir);
247 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
252 static char *getDefaultMusicDir(char *music_subdir)
254 static char *music_dir = NULL;
256 if (music_subdir == NULL)
257 return options.music_directory;
259 checked_free(music_dir);
261 music_dir = getPath2(options.music_directory, music_subdir);
266 static char *getClassicArtworkSet(int type)
268 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
269 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
270 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
273 static char *getClassicArtworkDir(int type)
275 return (type == TREE_TYPE_GRAPHICS_DIR ?
276 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_SOUNDS_DIR ?
278 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
279 type == TREE_TYPE_MUSIC_DIR ?
280 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
283 static char *getUserGraphicsDir()
285 static char *usergraphics_dir = NULL;
287 if (usergraphics_dir == NULL)
288 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
290 return usergraphics_dir;
293 static char *getUserSoundsDir()
295 static char *usersounds_dir = NULL;
297 if (usersounds_dir == NULL)
298 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
300 return usersounds_dir;
303 static char *getUserMusicDir()
305 static char *usermusic_dir = NULL;
307 if (usermusic_dir == NULL)
308 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
310 return usermusic_dir;
313 static char *getSetupArtworkDir(TreeInfo *ti)
315 static char *artwork_dir = NULL;
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);
1094 node_new = getTreeInfoCopy(node); /* copy complete node */
1096 node_new->node_top = node_top; /* correct top node link */
1097 node_new->node_parent = node_parent; /* correct parent node link */
1099 if (node->level_group)
1100 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1101 skip_sets_without_levels);
1103 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1104 skip_sets_without_levels);
1109 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1111 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1113 *ti_new = ti_cloned;
1116 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1118 boolean settings_changed = FALSE;
1122 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1123 !strEqual(node->graphics_set, node->graphics_set_ecs))
1125 setString(&node->graphics_set, node->graphics_set_ecs);
1126 settings_changed = TRUE;
1128 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1129 !strEqual(node->graphics_set, node->graphics_set_aga))
1131 setString(&node->graphics_set, node->graphics_set_aga);
1132 settings_changed = TRUE;
1135 if (node->node_group != NULL)
1136 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1141 return settings_changed;
1144 void dumpTreeInfo(TreeInfo *node, int depth)
1148 printf("Dumping TreeInfo:\n");
1152 for (i = 0; i < (depth + 1) * 3; i++)
1155 printf("'%s' / '%s'\n", node->identifier, node->name);
1158 // use for dumping artwork info tree
1159 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1160 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1163 if (node->node_group != NULL)
1164 dumpTreeInfo(node->node_group, depth + 1);
1170 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1171 int (*compare_function)(const void *,
1174 int num_nodes = numTreeInfo(*node_first);
1175 TreeInfo **sort_array;
1176 TreeInfo *node = *node_first;
1182 /* allocate array for sorting structure pointers */
1183 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1185 /* writing structure pointers to sorting array */
1186 while (i < num_nodes && node) /* double boundary check... */
1188 sort_array[i] = node;
1194 /* sorting the structure pointers in the sorting array */
1195 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1198 /* update the linkage of list elements with the sorted node array */
1199 for (i = 0; i < num_nodes - 1; i++)
1200 sort_array[i]->next = sort_array[i + 1];
1201 sort_array[num_nodes - 1]->next = NULL;
1203 /* update the linkage of the main list anchor pointer */
1204 *node_first = sort_array[0];
1208 /* now recursively sort the level group structures */
1212 if (node->node_group != NULL)
1213 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1219 void sortTreeInfo(TreeInfo **node_first)
1221 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1225 /* ========================================================================= */
1226 /* some stuff from "files.c" */
1227 /* ========================================================================= */
1229 #if defined(PLATFORM_WIN32)
1231 #define S_IRGRP S_IRUSR
1234 #define S_IROTH S_IRUSR
1237 #define S_IWGRP S_IWUSR
1240 #define S_IWOTH S_IWUSR
1243 #define S_IXGRP S_IXUSR
1246 #define S_IXOTH S_IXUSR
1249 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1254 #endif /* PLATFORM_WIN32 */
1256 /* file permissions for newly written files */
1257 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1258 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1259 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1261 #define MODE_W_PRIVATE (S_IWUSR)
1262 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1263 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1265 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1266 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1268 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1269 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1273 static char *dir = NULL;
1275 #if defined(PLATFORM_WIN32)
1278 dir = checked_malloc(MAX_PATH + 1);
1280 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1283 #elif defined(PLATFORM_UNIX)
1286 if ((dir = getenv("HOME")) == NULL)
1290 if ((pwd = getpwuid(getuid())) != NULL)
1291 dir = getStringCopy(pwd->pw_dir);
1303 char *getCommonDataDir(void)
1305 static char *common_data_dir = NULL;
1307 #if defined(PLATFORM_WIN32)
1308 if (common_data_dir == NULL)
1310 char *dir = checked_malloc(MAX_PATH + 1);
1312 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1313 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1314 common_data_dir = getPath2(dir, program.userdata_subdir);
1316 common_data_dir = options.rw_base_directory;
1319 if (common_data_dir == NULL)
1320 common_data_dir = options.rw_base_directory;
1323 return common_data_dir;
1326 char *getPersonalDataDir(void)
1328 static char *personal_data_dir = NULL;
1330 #if defined(PLATFORM_MACOSX)
1331 if (personal_data_dir == NULL)
1332 personal_data_dir = getPath2(getHomeDir(), "Documents");
1334 if (personal_data_dir == NULL)
1335 personal_data_dir = getHomeDir();
1338 return personal_data_dir;
1341 char *getUserGameDataDir(void)
1343 static char *user_game_data_dir = NULL;
1345 #if defined(PLATFORM_ANDROID)
1346 if (user_game_data_dir == NULL)
1347 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1349 if (user_game_data_dir == NULL)
1350 user_game_data_dir = getPath2(getPersonalDataDir(),
1351 program.userdata_subdir);
1354 return user_game_data_dir;
1357 void updateUserGameDataDir()
1359 #if defined(PLATFORM_MACOSX)
1360 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1361 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1363 /* convert old Unix style game data directory to Mac OS X style, if needed */
1364 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1366 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1368 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1369 userdata_dir_old, userdata_dir_new);
1371 /* continue using Unix style data directory -- this should not happen */
1372 program.userdata_path = getPath2(getPersonalDataDir(),
1373 program.userdata_subdir_unix);
1377 free(userdata_dir_old);
1383 return getUserGameDataDir();
1386 static mode_t posix_umask(mode_t mask)
1388 #if defined(PLATFORM_UNIX)
1395 static int posix_mkdir(const char *pathname, mode_t mode)
1397 #if defined(PLATFORM_WIN32)
1398 return mkdir(pathname);
1400 return mkdir(pathname, mode);
1404 static boolean posix_process_running_setgid()
1406 #if defined(PLATFORM_UNIX)
1407 return (getgid() != getegid());
1413 void createDirectory(char *dir, char *text, int permission_class)
1415 /* leave "other" permissions in umask untouched, but ensure group parts
1416 of USERDATA_DIR_MODE are not masked */
1417 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1418 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1419 mode_t last_umask = posix_umask(0);
1420 mode_t group_umask = ~(dir_mode & S_IRWXG);
1421 int running_setgid = posix_process_running_setgid();
1423 /* if we're setgid, protect files against "other" */
1424 /* else keep umask(0) to make the dir world-writable */
1427 posix_umask(last_umask & group_umask);
1429 dir_mode |= MODE_W_ALL;
1431 if (!directoryExists(dir))
1432 if (posix_mkdir(dir, dir_mode) != 0)
1433 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1434 text, dir, strerror(errno));
1436 if (permission_class == PERMS_PUBLIC && !running_setgid)
1437 chmod(dir, dir_mode);
1439 posix_umask(last_umask); /* restore previous umask */
1442 void InitUserDataDirectory()
1444 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1447 void SetFilePermissions(char *filename, int permission_class)
1449 int running_setgid = posix_process_running_setgid();
1450 int perms = (permission_class == PERMS_PRIVATE ?
1451 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1453 if (permission_class == PERMS_PUBLIC && !running_setgid)
1454 perms |= MODE_W_ALL;
1456 chmod(filename, perms);
1459 char *getCookie(char *file_type)
1461 static char cookie[MAX_COOKIE_LEN + 1];
1463 if (strlen(program.cookie_prefix) + 1 +
1464 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1465 return "[COOKIE ERROR]"; /* should never happen */
1467 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1468 program.cookie_prefix, file_type,
1469 program.version_major, program.version_minor);
1474 int getFileVersionFromCookieString(const char *cookie)
1476 const char *ptr_cookie1, *ptr_cookie2;
1477 const char *pattern1 = "_FILE_VERSION_";
1478 const char *pattern2 = "?.?";
1479 const int len_cookie = strlen(cookie);
1480 const int len_pattern1 = strlen(pattern1);
1481 const int len_pattern2 = strlen(pattern2);
1482 const int len_pattern = len_pattern1 + len_pattern2;
1483 int version_major, version_minor;
1485 if (len_cookie <= len_pattern)
1488 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1489 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1491 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1494 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1495 ptr_cookie2[1] != '.' ||
1496 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1499 version_major = ptr_cookie2[0] - '0';
1500 version_minor = ptr_cookie2[2] - '0';
1502 return VERSION_IDENT(version_major, version_minor, 0, 0);
1505 boolean checkCookieString(const char *cookie, const char *template)
1507 const char *pattern = "_FILE_VERSION_?.?";
1508 const int len_cookie = strlen(cookie);
1509 const int len_template = strlen(template);
1510 const int len_pattern = strlen(pattern);
1512 if (len_cookie != len_template)
1515 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1521 /* ------------------------------------------------------------------------- */
1522 /* setup file list and hash handling functions */
1523 /* ------------------------------------------------------------------------- */
1525 char *getFormattedSetupEntry(char *token, char *value)
1528 static char entry[MAX_LINE_LEN];
1530 /* if value is an empty string, just return token without value */
1534 /* start with the token and some spaces to format output line */
1535 sprintf(entry, "%s:", token);
1536 for (i = strlen(entry); i < token_value_position; i++)
1539 /* continue with the token's value */
1540 strcat(entry, value);
1545 SetupFileList *newSetupFileList(char *token, char *value)
1547 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1549 new->token = getStringCopy(token);
1550 new->value = getStringCopy(value);
1557 void freeSetupFileList(SetupFileList *list)
1562 checked_free(list->token);
1563 checked_free(list->value);
1566 freeSetupFileList(list->next);
1571 char *getListEntry(SetupFileList *list, char *token)
1576 if (strEqual(list->token, token))
1579 return getListEntry(list->next, token);
1582 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1587 if (strEqual(list->token, token))
1589 checked_free(list->value);
1591 list->value = getStringCopy(value);
1595 else if (list->next == NULL)
1596 return (list->next = newSetupFileList(token, value));
1598 return setListEntry(list->next, token, value);
1601 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1606 if (list->next == NULL)
1607 return (list->next = newSetupFileList(token, value));
1609 return addListEntry(list->next, token, value);
1612 #if ENABLE_UNUSED_CODE
1614 static void printSetupFileList(SetupFileList *list)
1619 printf("token: '%s'\n", list->token);
1620 printf("value: '%s'\n", list->value);
1622 printSetupFileList(list->next);
1628 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1629 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1630 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1631 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1633 #define insert_hash_entry hashtable_insert
1634 #define search_hash_entry hashtable_search
1635 #define change_hash_entry hashtable_change
1636 #define remove_hash_entry hashtable_remove
1639 unsigned int get_hash_from_key(void *key)
1644 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1645 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1646 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1647 it works better than many other constants, prime or not) has never been
1648 adequately explained.
1650 If you just want to have a good hash function, and cannot wait, djb2
1651 is one of the best string hash functions i know. It has excellent
1652 distribution and speed on many different sets of keys and table sizes.
1653 You are not likely to do better with one of the "well known" functions
1654 such as PJW, K&R, etc.
1656 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1659 char *str = (char *)key;
1660 unsigned int hash = 5381;
1663 while ((c = *str++))
1664 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1669 static int keys_are_equal(void *key1, void *key2)
1671 return (strEqual((char *)key1, (char *)key2));
1674 SetupFileHash *newSetupFileHash()
1676 SetupFileHash *new_hash =
1677 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1679 if (new_hash == NULL)
1680 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1685 void freeSetupFileHash(SetupFileHash *hash)
1690 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1693 char *getHashEntry(SetupFileHash *hash, char *token)
1698 return search_hash_entry(hash, token);
1701 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1708 value_copy = getStringCopy(value);
1710 /* change value; if it does not exist, insert it as new */
1711 if (!change_hash_entry(hash, token, value_copy))
1712 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1713 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1716 char *removeHashEntry(SetupFileHash *hash, char *token)
1721 return remove_hash_entry(hash, token);
1724 #if ENABLE_UNUSED_CODE
1726 static void printSetupFileHash(SetupFileHash *hash)
1728 BEGIN_HASH_ITERATION(hash, itr)
1730 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1731 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1733 END_HASH_ITERATION(hash, itr)
1738 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1739 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1740 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1742 static boolean token_value_separator_found = FALSE;
1743 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1744 static boolean token_value_separator_warning = FALSE;
1746 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1747 static boolean token_already_exists_warning = FALSE;
1750 static boolean getTokenValueFromSetupLineExt(char *line,
1751 char **token_ptr, char **value_ptr,
1752 char *filename, char *line_raw,
1754 boolean separator_required)
1756 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1757 char *token, *value, *line_ptr;
1759 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1760 if (line_raw == NULL)
1762 strncpy(line_copy, line, MAX_LINE_LEN);
1763 line_copy[MAX_LINE_LEN] = '\0';
1766 strcpy(line_raw_copy, line_copy);
1767 line_raw = line_raw_copy;
1770 /* cut trailing comment from input line */
1771 for (line_ptr = line; *line_ptr; line_ptr++)
1773 if (*line_ptr == '#')
1780 /* cut trailing whitespaces from input line */
1781 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1782 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1785 /* ignore empty lines */
1789 /* cut leading whitespaces from token */
1790 for (token = line; *token; token++)
1791 if (*token != ' ' && *token != '\t')
1794 /* start with empty value as reliable default */
1797 token_value_separator_found = FALSE;
1799 /* find end of token to determine start of value */
1800 for (line_ptr = token; *line_ptr; line_ptr++)
1802 /* first look for an explicit token/value separator, like ':' or '=' */
1803 if (*line_ptr == ':' || *line_ptr == '=')
1805 *line_ptr = '\0'; /* terminate token string */
1806 value = line_ptr + 1; /* set beginning of value */
1808 token_value_separator_found = TRUE;
1814 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1815 /* fallback: if no token/value separator found, also allow whitespaces */
1816 if (!token_value_separator_found && !separator_required)
1818 for (line_ptr = token; *line_ptr; line_ptr++)
1820 if (*line_ptr == ' ' || *line_ptr == '\t')
1822 *line_ptr = '\0'; /* terminate token string */
1823 value = line_ptr + 1; /* set beginning of value */
1825 token_value_separator_found = TRUE;
1831 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1832 if (token_value_separator_found)
1834 if (!token_value_separator_warning)
1836 Error(ERR_INFO_LINE, "-");
1838 if (filename != NULL)
1840 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1841 Error(ERR_INFO, "- config file: '%s'", filename);
1845 Error(ERR_WARN, "missing token/value separator(s):");
1848 token_value_separator_warning = TRUE;
1851 if (filename != NULL)
1852 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1854 Error(ERR_INFO, "- line: '%s'", line_raw);
1860 /* cut trailing whitespaces from token */
1861 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1862 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1865 /* cut leading whitespaces from value */
1866 for (; *value; value++)
1867 if (*value != ' ' && *value != '\t')
1876 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1878 /* while the internal (old) interface does not require a token/value
1879 separator (for downwards compatibility with existing files which
1880 don't use them), it is mandatory for the external (new) interface */
1882 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1885 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1886 boolean top_recursion_level, boolean is_hash)
1888 static SetupFileHash *include_filename_hash = NULL;
1889 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1890 char *token, *value, *line_ptr;
1891 void *insert_ptr = NULL;
1892 boolean read_continued_line = FALSE;
1894 int line_nr = 0, token_count = 0, include_count = 0;
1896 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1897 token_value_separator_warning = FALSE;
1900 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1901 token_already_exists_warning = FALSE;
1904 if (!(file = openFile(filename, MODE_READ)))
1906 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1911 /* use "insert pointer" to store list end for constant insertion complexity */
1913 insert_ptr = setup_file_data;
1915 /* on top invocation, create hash to mark included files (to prevent loops) */
1916 if (top_recursion_level)
1917 include_filename_hash = newSetupFileHash();
1919 /* mark this file as already included (to prevent including it again) */
1920 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1922 while (!checkEndOfFile(file))
1924 /* read next line of input file */
1925 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1928 /* check if line was completely read and is terminated by line break */
1929 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1932 /* cut trailing line break (this can be newline and/or carriage return) */
1933 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1934 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1937 /* copy raw input line for later use (mainly debugging output) */
1938 strcpy(line_raw, line);
1940 if (read_continued_line)
1942 /* append new line to existing line, if there is enough space */
1943 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1944 strcat(previous_line, line_ptr);
1946 strcpy(line, previous_line); /* copy storage buffer to line */
1948 read_continued_line = FALSE;
1951 /* if the last character is '\', continue at next line */
1952 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1954 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1955 strcpy(previous_line, line); /* copy line to storage buffer */
1957 read_continued_line = TRUE;
1962 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1963 line_raw, line_nr, FALSE))
1968 if (strEqual(token, "include"))
1970 if (getHashEntry(include_filename_hash, value) == NULL)
1972 char *basepath = getBasePath(filename);
1973 char *basename = getBaseName(value);
1974 char *filename_include = getPath2(basepath, basename);
1976 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1980 free(filename_include);
1986 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1993 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1995 getHashEntry((SetupFileHash *)setup_file_data, token);
1997 if (old_value != NULL)
1999 if (!token_already_exists_warning)
2001 Error(ERR_INFO_LINE, "-");
2002 Error(ERR_WARN, "duplicate token(s) found in config file:");
2003 Error(ERR_INFO, "- config file: '%s'", filename);
2005 token_already_exists_warning = TRUE;
2008 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2009 Error(ERR_INFO, " old value: '%s'", old_value);
2010 Error(ERR_INFO, " new value: '%s'", value);
2014 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2018 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2028 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2029 if (token_value_separator_warning)
2030 Error(ERR_INFO_LINE, "-");
2033 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2034 if (token_already_exists_warning)
2035 Error(ERR_INFO_LINE, "-");
2038 if (token_count == 0 && include_count == 0)
2039 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2041 if (top_recursion_level)
2042 freeSetupFileHash(include_filename_hash);
2047 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2051 if (!(file = fopen(filename, MODE_WRITE)))
2053 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2058 BEGIN_HASH_ITERATION(hash, itr)
2060 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2061 HASH_ITERATION_VALUE(itr)));
2063 END_HASH_ITERATION(hash, itr)
2068 SetupFileList *loadSetupFileList(char *filename)
2070 SetupFileList *setup_file_list = newSetupFileList("", "");
2071 SetupFileList *first_valid_list_entry;
2073 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2075 freeSetupFileList(setup_file_list);
2080 first_valid_list_entry = setup_file_list->next;
2082 /* free empty list header */
2083 setup_file_list->next = NULL;
2084 freeSetupFileList(setup_file_list);
2086 return first_valid_list_entry;
2089 SetupFileHash *loadSetupFileHash(char *filename)
2091 SetupFileHash *setup_file_hash = newSetupFileHash();
2093 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2095 freeSetupFileHash(setup_file_hash);
2100 return setup_file_hash;
2103 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2104 char *filename, char *identifier)
2106 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2109 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2110 else if (!checkCookieString(value, identifier))
2111 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2115 /* ========================================================================= */
2116 /* setup file stuff */
2117 /* ========================================================================= */
2119 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2120 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2121 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2123 /* level directory info */
2124 #define LEVELINFO_TOKEN_IDENTIFIER 0
2125 #define LEVELINFO_TOKEN_NAME 1
2126 #define LEVELINFO_TOKEN_NAME_SORTING 2
2127 #define LEVELINFO_TOKEN_AUTHOR 3
2128 #define LEVELINFO_TOKEN_YEAR 4
2129 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2130 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2131 #define LEVELINFO_TOKEN_TESTED_BY 7
2132 #define LEVELINFO_TOKEN_LEVELS 8
2133 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2134 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2135 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2136 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2137 #define LEVELINFO_TOKEN_READONLY 13
2138 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2139 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2140 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2141 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2142 #define LEVELINFO_TOKEN_MUSIC_SET 18
2143 #define LEVELINFO_TOKEN_FILENAME 19
2144 #define LEVELINFO_TOKEN_FILETYPE 20
2145 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2146 #define LEVELINFO_TOKEN_HANDICAP 22
2147 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2149 #define NUM_LEVELINFO_TOKENS 24
2151 static LevelDirTree ldi;
2153 static struct TokenInfo levelinfo_tokens[] =
2155 /* level directory info */
2156 { TYPE_STRING, &ldi.identifier, "identifier" },
2157 { TYPE_STRING, &ldi.name, "name" },
2158 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2159 { TYPE_STRING, &ldi.author, "author" },
2160 { TYPE_STRING, &ldi.year, "year" },
2161 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2162 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2163 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2164 { TYPE_INTEGER, &ldi.levels, "levels" },
2165 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2166 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2167 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2168 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2169 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2170 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2171 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2172 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2173 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2174 { TYPE_STRING, &ldi.music_set, "music_set" },
2175 { TYPE_STRING, &ldi.level_filename, "filename" },
2176 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2177 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2178 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2179 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2182 static struct TokenInfo artworkinfo_tokens[] =
2184 /* artwork directory info */
2185 { TYPE_STRING, &ldi.identifier, "identifier" },
2186 { TYPE_STRING, &ldi.subdir, "subdir" },
2187 { TYPE_STRING, &ldi.name, "name" },
2188 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2189 { TYPE_STRING, &ldi.author, "author" },
2190 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2191 { TYPE_STRING, &ldi.basepath, "basepath" },
2192 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2193 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2194 { TYPE_INTEGER, &ldi.color, "color" },
2195 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2200 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2204 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2205 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2206 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2207 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2210 ti->node_parent = NULL;
2211 ti->node_group = NULL;
2218 ti->fullpath = NULL;
2219 ti->basepath = NULL;
2220 ti->identifier = NULL;
2221 ti->name = getStringCopy(ANONYMOUS_NAME);
2222 ti->name_sorting = NULL;
2223 ti->author = getStringCopy(ANONYMOUS_NAME);
2226 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2227 ti->latest_engine = FALSE; /* default: get from level */
2228 ti->parent_link = FALSE;
2229 ti->in_user_dir = FALSE;
2230 ti->user_defined = FALSE;
2232 ti->class_desc = NULL;
2234 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2236 if (ti->type == TREE_TYPE_LEVEL_DIR)
2238 ti->imported_from = NULL;
2239 ti->imported_by = NULL;
2240 ti->tested_by = NULL;
2242 ti->graphics_set_ecs = NULL;
2243 ti->graphics_set_aga = NULL;
2244 ti->graphics_set = NULL;
2245 ti->sounds_set = NULL;
2246 ti->music_set = NULL;
2247 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2248 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2249 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2251 ti->level_filename = NULL;
2252 ti->level_filetype = NULL;
2254 ti->special_flags = NULL;
2257 ti->first_level = 0;
2259 ti->level_group = FALSE;
2260 ti->handicap_level = 0;
2261 ti->readonly = TRUE;
2262 ti->handicap = TRUE;
2263 ti->skip_levels = FALSE;
2267 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2271 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2273 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2278 /* copy all values from the parent structure */
2280 ti->type = parent->type;
2282 ti->node_top = parent->node_top;
2283 ti->node_parent = parent;
2284 ti->node_group = NULL;
2291 ti->fullpath = NULL;
2292 ti->basepath = NULL;
2293 ti->identifier = NULL;
2294 ti->name = getStringCopy(ANONYMOUS_NAME);
2295 ti->name_sorting = NULL;
2296 ti->author = getStringCopy(parent->author);
2297 ti->year = getStringCopy(parent->year);
2299 ti->sort_priority = parent->sort_priority;
2300 ti->latest_engine = parent->latest_engine;
2301 ti->parent_link = FALSE;
2302 ti->in_user_dir = parent->in_user_dir;
2303 ti->user_defined = parent->user_defined;
2304 ti->color = parent->color;
2305 ti->class_desc = getStringCopy(parent->class_desc);
2307 ti->infotext = getStringCopy(parent->infotext);
2309 if (ti->type == TREE_TYPE_LEVEL_DIR)
2311 ti->imported_from = getStringCopy(parent->imported_from);
2312 ti->imported_by = getStringCopy(parent->imported_by);
2313 ti->tested_by = getStringCopy(parent->tested_by);
2315 ti->graphics_set_ecs = NULL;
2316 ti->graphics_set_aga = NULL;
2317 ti->graphics_set = NULL;
2318 ti->sounds_set = NULL;
2319 ti->music_set = NULL;
2320 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2321 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2322 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2324 ti->level_filename = NULL;
2325 ti->level_filetype = NULL;
2327 ti->special_flags = getStringCopy(parent->special_flags);
2330 ti->first_level = 0;
2332 ti->level_group = FALSE;
2333 ti->handicap_level = 0;
2334 ti->readonly = parent->readonly;
2335 ti->handicap = TRUE;
2336 ti->skip_levels = FALSE;
2340 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2342 TreeInfo *ti_copy = newTreeInfo();
2344 /* copy all values from the original structure */
2346 ti_copy->type = ti->type;
2348 ti_copy->node_top = ti->node_top;
2349 ti_copy->node_parent = ti->node_parent;
2350 ti_copy->node_group = ti->node_group;
2351 ti_copy->next = ti->next;
2353 ti_copy->cl_first = ti->cl_first;
2354 ti_copy->cl_cursor = ti->cl_cursor;
2356 ti_copy->subdir = getStringCopy(ti->subdir);
2357 ti_copy->fullpath = getStringCopy(ti->fullpath);
2358 ti_copy->basepath = getStringCopy(ti->basepath);
2359 ti_copy->identifier = getStringCopy(ti->identifier);
2360 ti_copy->name = getStringCopy(ti->name);
2361 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2362 ti_copy->author = getStringCopy(ti->author);
2363 ti_copy->year = getStringCopy(ti->year);
2364 ti_copy->imported_from = getStringCopy(ti->imported_from);
2365 ti_copy->imported_by = getStringCopy(ti->imported_by);
2366 ti_copy->tested_by = getStringCopy(ti->tested_by);
2368 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2369 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2370 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2371 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2372 ti_copy->music_set = getStringCopy(ti->music_set);
2373 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2374 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2375 ti_copy->music_path = getStringCopy(ti->music_path);
2377 ti_copy->level_filename = getStringCopy(ti->level_filename);
2378 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2380 ti_copy->special_flags = getStringCopy(ti->special_flags);
2382 ti_copy->levels = ti->levels;
2383 ti_copy->first_level = ti->first_level;
2384 ti_copy->last_level = ti->last_level;
2385 ti_copy->sort_priority = ti->sort_priority;
2387 ti_copy->latest_engine = ti->latest_engine;
2389 ti_copy->level_group = ti->level_group;
2390 ti_copy->parent_link = ti->parent_link;
2391 ti_copy->in_user_dir = ti->in_user_dir;
2392 ti_copy->user_defined = ti->user_defined;
2393 ti_copy->readonly = ti->readonly;
2394 ti_copy->handicap = ti->handicap;
2395 ti_copy->skip_levels = ti->skip_levels;
2397 ti_copy->color = ti->color;
2398 ti_copy->class_desc = getStringCopy(ti->class_desc);
2399 ti_copy->handicap_level = ti->handicap_level;
2401 ti_copy->infotext = getStringCopy(ti->infotext);
2406 void freeTreeInfo(TreeInfo *ti)
2411 checked_free(ti->subdir);
2412 checked_free(ti->fullpath);
2413 checked_free(ti->basepath);
2414 checked_free(ti->identifier);
2416 checked_free(ti->name);
2417 checked_free(ti->name_sorting);
2418 checked_free(ti->author);
2419 checked_free(ti->year);
2421 checked_free(ti->class_desc);
2423 checked_free(ti->infotext);
2425 if (ti->type == TREE_TYPE_LEVEL_DIR)
2427 checked_free(ti->imported_from);
2428 checked_free(ti->imported_by);
2429 checked_free(ti->tested_by);
2431 checked_free(ti->graphics_set_ecs);
2432 checked_free(ti->graphics_set_aga);
2433 checked_free(ti->graphics_set);
2434 checked_free(ti->sounds_set);
2435 checked_free(ti->music_set);
2437 checked_free(ti->graphics_path);
2438 checked_free(ti->sounds_path);
2439 checked_free(ti->music_path);
2441 checked_free(ti->level_filename);
2442 checked_free(ti->level_filetype);
2444 checked_free(ti->special_flags);
2447 // recursively free child node
2449 freeTreeInfo(ti->node_group);
2451 // recursively free next node
2453 freeTreeInfo(ti->next);
2458 void setSetupInfo(struct TokenInfo *token_info,
2459 int token_nr, char *token_value)
2461 int token_type = token_info[token_nr].type;
2462 void *setup_value = token_info[token_nr].value;
2464 if (token_value == NULL)
2467 /* set setup field to corresponding token value */
2472 *(boolean *)setup_value = get_boolean_from_string(token_value);
2476 *(int *)setup_value = get_switch3_from_string(token_value);
2480 *(Key *)setup_value = getKeyFromKeyName(token_value);
2484 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2488 *(int *)setup_value = get_integer_from_string(token_value);
2492 checked_free(*(char **)setup_value);
2493 *(char **)setup_value = getStringCopy(token_value);
2501 static int compareTreeInfoEntries(const void *object1, const void *object2)
2503 const TreeInfo *entry1 = *((TreeInfo **)object1);
2504 const TreeInfo *entry2 = *((TreeInfo **)object2);
2505 int class_sorting1 = 0, class_sorting2 = 0;
2508 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2510 class_sorting1 = LEVELSORTING(entry1);
2511 class_sorting2 = LEVELSORTING(entry2);
2513 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2514 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2515 entry1->type == TREE_TYPE_MUSIC_DIR)
2517 class_sorting1 = ARTWORKSORTING(entry1);
2518 class_sorting2 = ARTWORKSORTING(entry2);
2521 if (entry1->parent_link || entry2->parent_link)
2522 compare_result = (entry1->parent_link ? -1 : +1);
2523 else if (entry1->sort_priority == entry2->sort_priority)
2525 char *name1 = getStringToLower(entry1->name_sorting);
2526 char *name2 = getStringToLower(entry2->name_sorting);
2528 compare_result = strcmp(name1, name2);
2533 else if (class_sorting1 == class_sorting2)
2534 compare_result = entry1->sort_priority - entry2->sort_priority;
2536 compare_result = class_sorting1 - class_sorting2;
2538 return compare_result;
2541 static void createParentTreeInfoNode(TreeInfo *node_parent)
2545 if (node_parent == NULL)
2548 ti_new = newTreeInfo();
2549 setTreeInfoToDefaults(ti_new, node_parent->type);
2551 ti_new->node_parent = node_parent;
2552 ti_new->parent_link = TRUE;
2554 setString(&ti_new->identifier, node_parent->identifier);
2555 setString(&ti_new->name, ".. (parent directory)");
2556 setString(&ti_new->name_sorting, ti_new->name);
2558 setString(&ti_new->subdir, "..");
2559 setString(&ti_new->fullpath, node_parent->fullpath);
2561 ti_new->sort_priority = node_parent->sort_priority;
2562 ti_new->latest_engine = node_parent->latest_engine;
2564 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2566 pushTreeInfo(&node_parent->node_group, ti_new);
2570 /* -------------------------------------------------------------------------- */
2571 /* functions for handling level and custom artwork info cache */
2572 /* -------------------------------------------------------------------------- */
2574 static void LoadArtworkInfoCache()
2576 InitCacheDirectory();
2578 if (artworkinfo_cache_old == NULL)
2580 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2582 /* try to load artwork info hash from already existing cache file */
2583 artworkinfo_cache_old = loadSetupFileHash(filename);
2585 /* if no artwork info cache file was found, start with empty hash */
2586 if (artworkinfo_cache_old == NULL)
2587 artworkinfo_cache_old = newSetupFileHash();
2592 if (artworkinfo_cache_new == NULL)
2593 artworkinfo_cache_new = newSetupFileHash();
2596 static void SaveArtworkInfoCache()
2598 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2600 InitCacheDirectory();
2602 saveSetupFileHash(artworkinfo_cache_new, filename);
2607 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2609 static char *prefix = NULL;
2611 checked_free(prefix);
2613 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2618 /* (identical to above function, but separate string buffer needed -- nasty) */
2619 static char *getCacheToken(char *prefix, char *suffix)
2621 static char *token = NULL;
2623 checked_free(token);
2625 token = getStringCat2WithSeparator(prefix, suffix, ".");
2630 static char *getFileTimestampString(char *filename)
2632 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2635 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2637 struct stat file_status;
2639 if (timestamp_string == NULL)
2642 if (stat(filename, &file_status) != 0) /* cannot stat file */
2645 return (file_status.st_mtime != atoi(timestamp_string));
2648 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2650 char *identifier = level_node->subdir;
2651 char *type_string = ARTWORK_DIRECTORY(type);
2652 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2653 char *token_main = getCacheToken(token_prefix, "CACHED");
2654 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2655 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2656 TreeInfo *artwork_info = NULL;
2658 if (!use_artworkinfo_cache)
2665 artwork_info = newTreeInfo();
2666 setTreeInfoToDefaults(artwork_info, type);
2668 /* set all structure fields according to the token/value pairs */
2669 ldi = *artwork_info;
2670 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2672 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2673 char *value = getHashEntry(artworkinfo_cache_old, token);
2675 setSetupInfo(artworkinfo_tokens, i, value);
2677 /* check if cache entry for this item is invalid or incomplete */
2680 Error(ERR_WARN, "cache entry '%s' invalid", token);
2686 *artwork_info = ldi;
2691 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2692 LEVELINFO_FILENAME);
2693 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2694 ARTWORKINFO_FILENAME(type));
2696 /* check if corresponding "levelinfo.conf" file has changed */
2697 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2698 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2700 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2703 /* check if corresponding "<artworkinfo>.conf" file has changed */
2704 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2705 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2707 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2710 checked_free(filename_levelinfo);
2711 checked_free(filename_artworkinfo);
2714 if (!cached && artwork_info != NULL)
2716 freeTreeInfo(artwork_info);
2721 return artwork_info;
2724 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2725 LevelDirTree *level_node, int type)
2727 char *identifier = level_node->subdir;
2728 char *type_string = ARTWORK_DIRECTORY(type);
2729 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2730 char *token_main = getCacheToken(token_prefix, "CACHED");
2731 boolean set_cache_timestamps = TRUE;
2734 setHashEntry(artworkinfo_cache_new, token_main, "true");
2736 if (set_cache_timestamps)
2738 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2739 LEVELINFO_FILENAME);
2740 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2741 ARTWORKINFO_FILENAME(type));
2742 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2743 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2745 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2746 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2748 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2749 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2751 checked_free(filename_levelinfo);
2752 checked_free(filename_artworkinfo);
2753 checked_free(timestamp_levelinfo);
2754 checked_free(timestamp_artworkinfo);
2757 ldi = *artwork_info;
2758 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2760 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2761 char *value = getSetupValue(artworkinfo_tokens[i].type,
2762 artworkinfo_tokens[i].value);
2764 setHashEntry(artworkinfo_cache_new, token, value);
2769 /* -------------------------------------------------------------------------- */
2770 /* functions for loading level info and custom artwork info */
2771 /* -------------------------------------------------------------------------- */
2773 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2774 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2776 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2777 TreeInfo *node_parent,
2778 char *level_directory,
2779 char *directory_name)
2781 char *directory_path = getPath2(level_directory, directory_name);
2782 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2783 SetupFileHash *setup_file_hash;
2784 LevelDirTree *leveldir_new = NULL;
2787 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2788 if (!options.debug && !fileExists(filename))
2790 free(directory_path);
2796 setup_file_hash = loadSetupFileHash(filename);
2798 if (setup_file_hash == NULL)
2800 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2802 free(directory_path);
2808 leveldir_new = newTreeInfo();
2811 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2813 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2815 leveldir_new->subdir = getStringCopy(directory_name);
2817 checkSetupFileHashIdentifier(setup_file_hash, filename,
2818 getCookie("LEVELINFO"));
2820 /* set all structure fields according to the token/value pairs */
2821 ldi = *leveldir_new;
2822 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2823 setSetupInfo(levelinfo_tokens, i,
2824 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2825 *leveldir_new = ldi;
2827 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2828 setString(&leveldir_new->name, leveldir_new->subdir);
2830 if (leveldir_new->identifier == NULL)
2831 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2833 if (leveldir_new->name_sorting == NULL)
2834 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2836 if (node_parent == NULL) /* top level group */
2838 leveldir_new->basepath = getStringCopy(level_directory);
2839 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2841 else /* sub level group */
2843 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2844 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2847 leveldir_new->last_level =
2848 leveldir_new->first_level + leveldir_new->levels - 1;
2850 leveldir_new->in_user_dir =
2851 (!strEqual(leveldir_new->basepath, options.level_directory));
2853 /* adjust some settings if user's private level directory was detected */
2854 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2855 leveldir_new->in_user_dir &&
2856 (strEqual(leveldir_new->subdir, getLoginName()) ||
2857 strEqual(leveldir_new->name, getLoginName()) ||
2858 strEqual(leveldir_new->author, getRealName())))
2860 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2861 leveldir_new->readonly = FALSE;
2864 leveldir_new->user_defined =
2865 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2867 leveldir_new->color = LEVELCOLOR(leveldir_new);
2869 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2871 leveldir_new->handicap_level = /* set handicap to default value */
2872 (leveldir_new->user_defined || !leveldir_new->handicap ?
2873 leveldir_new->last_level : leveldir_new->first_level);
2875 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
2876 leveldir_new->level_group);
2878 pushTreeInfo(node_first, leveldir_new);
2880 freeSetupFileHash(setup_file_hash);
2882 if (leveldir_new->level_group)
2884 /* create node to link back to current level directory */
2885 createParentTreeInfoNode(leveldir_new);
2887 /* recursively step into sub-directory and look for more level series */
2888 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2889 leveldir_new, directory_path);
2892 free(directory_path);
2898 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2899 TreeInfo *node_parent,
2900 char *level_directory)
2903 DirectoryEntry *dir_entry;
2904 boolean valid_entry_found = FALSE;
2906 if ((dir = openDirectory(level_directory)) == NULL)
2908 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2913 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2915 char *directory_name = dir_entry->basename;
2916 char *directory_path = getPath2(level_directory, directory_name);
2918 /* skip entries for current and parent directory */
2919 if (strEqual(directory_name, ".") ||
2920 strEqual(directory_name, ".."))
2922 free(directory_path);
2927 /* find out if directory entry is itself a directory */
2928 if (!dir_entry->is_directory) /* not a directory */
2930 free(directory_path);
2935 free(directory_path);
2937 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2938 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2939 strEqual(directory_name, MUSIC_DIRECTORY))
2942 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2947 closeDirectory(dir);
2949 /* special case: top level directory may directly contain "levelinfo.conf" */
2950 if (node_parent == NULL && !valid_entry_found)
2952 /* check if this directory directly contains a file "levelinfo.conf" */
2953 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2954 level_directory, ".");
2957 if (!valid_entry_found)
2958 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2962 boolean AdjustGraphicsForEMC()
2964 boolean settings_changed = FALSE;
2966 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
2967 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
2969 return settings_changed;
2972 void LoadLevelInfo()
2974 InitUserLevelDirectory(getLoginName());
2976 DrawInitText("Loading level series", 120, FC_GREEN);
2978 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2979 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2981 /* after loading all level set information, clone the level directory tree
2982 and remove all level sets without levels (these may still contain artwork
2983 to be offered in the setup menu as "custom artwork", and are therefore
2984 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
2985 leveldir_first_all = leveldir_first;
2986 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
2988 AdjustGraphicsForEMC();
2990 /* before sorting, the first entries will be from the user directory */
2991 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2993 if (leveldir_first == NULL)
2994 Error(ERR_EXIT, "cannot find any valid level series in any directory");
2996 sortTreeInfo(&leveldir_first);
2998 #if ENABLE_UNUSED_CODE
2999 dumpTreeInfo(leveldir_first, 0);
3003 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3004 TreeInfo *node_parent,
3005 char *base_directory,
3006 char *directory_name, int type)
3008 char *directory_path = getPath2(base_directory, directory_name);
3009 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3010 SetupFileHash *setup_file_hash = NULL;
3011 TreeInfo *artwork_new = NULL;
3014 if (fileExists(filename))
3015 setup_file_hash = loadSetupFileHash(filename);
3017 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3020 DirectoryEntry *dir_entry;
3021 boolean valid_file_found = FALSE;
3023 if ((dir = openDirectory(directory_path)) != NULL)
3025 while ((dir_entry = readDirectory(dir)) != NULL)
3027 char *entry_name = dir_entry->basename;
3029 if (FileIsArtworkType(entry_name, type))
3031 valid_file_found = TRUE;
3037 closeDirectory(dir);
3040 if (!valid_file_found)
3042 if (!strEqual(directory_name, "."))
3043 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3045 free(directory_path);
3052 artwork_new = newTreeInfo();
3055 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3057 setTreeInfoToDefaults(artwork_new, type);
3059 artwork_new->subdir = getStringCopy(directory_name);
3061 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3063 /* set all structure fields according to the token/value pairs */
3065 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3066 setSetupInfo(levelinfo_tokens, i,
3067 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3070 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3071 setString(&artwork_new->name, artwork_new->subdir);
3073 if (artwork_new->identifier == NULL)
3074 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3076 if (artwork_new->name_sorting == NULL)
3077 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3080 if (node_parent == NULL) /* top level group */
3082 artwork_new->basepath = getStringCopy(base_directory);
3083 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3085 else /* sub level group */
3087 artwork_new->basepath = getStringCopy(node_parent->basepath);
3088 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3091 artwork_new->in_user_dir =
3092 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3094 /* (may use ".sort_priority" from "setup_file_hash" above) */
3095 artwork_new->color = ARTWORKCOLOR(artwork_new);
3097 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3099 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3101 if (strEqual(artwork_new->subdir, "."))
3103 if (artwork_new->user_defined)
3105 setString(&artwork_new->identifier, "private");
3106 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3110 setString(&artwork_new->identifier, "classic");
3111 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3114 /* set to new values after changing ".sort_priority" */
3115 artwork_new->color = ARTWORKCOLOR(artwork_new);
3117 setString(&artwork_new->class_desc,
3118 getLevelClassDescription(artwork_new));
3122 setString(&artwork_new->identifier, artwork_new->subdir);
3125 setString(&artwork_new->name, artwork_new->identifier);
3126 setString(&artwork_new->name_sorting, artwork_new->name);
3129 pushTreeInfo(node_first, artwork_new);
3131 freeSetupFileHash(setup_file_hash);
3133 free(directory_path);
3139 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3140 TreeInfo *node_parent,
3141 char *base_directory, int type)
3144 DirectoryEntry *dir_entry;
3145 boolean valid_entry_found = FALSE;
3147 if ((dir = openDirectory(base_directory)) == NULL)
3149 /* display error if directory is main "options.graphics_directory" etc. */
3150 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3151 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3156 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3158 char *directory_name = dir_entry->basename;
3159 char *directory_path = getPath2(base_directory, directory_name);
3161 /* skip directory entries for current and parent directory */
3162 if (strEqual(directory_name, ".") ||
3163 strEqual(directory_name, ".."))
3165 free(directory_path);
3170 /* skip directory entries which are not a directory */
3171 if (!dir_entry->is_directory) /* not a directory */
3173 free(directory_path);
3178 free(directory_path);
3180 /* check if this directory contains artwork with or without config file */
3181 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3183 directory_name, type);
3186 closeDirectory(dir);
3188 /* check if this directory directly contains artwork itself */
3189 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3190 base_directory, ".",
3192 if (!valid_entry_found)
3193 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3197 static TreeInfo *getDummyArtworkInfo(int type)
3199 /* this is only needed when there is completely no artwork available */
3200 TreeInfo *artwork_new = newTreeInfo();
3202 setTreeInfoToDefaults(artwork_new, type);
3204 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3205 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3206 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3208 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3209 setString(&artwork_new->name, UNDEFINED_FILENAME);
3210 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3215 void LoadArtworkInfo()
3217 LoadArtworkInfoCache();
3219 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3221 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3222 options.graphics_directory,
3223 TREE_TYPE_GRAPHICS_DIR);
3224 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3225 getUserGraphicsDir(),
3226 TREE_TYPE_GRAPHICS_DIR);
3228 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3229 options.sounds_directory,
3230 TREE_TYPE_SOUNDS_DIR);
3231 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3233 TREE_TYPE_SOUNDS_DIR);
3235 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3236 options.music_directory,
3237 TREE_TYPE_MUSIC_DIR);
3238 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3240 TREE_TYPE_MUSIC_DIR);
3242 if (artwork.gfx_first == NULL)
3243 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3244 if (artwork.snd_first == NULL)
3245 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3246 if (artwork.mus_first == NULL)
3247 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3249 /* before sorting, the first entries will be from the user directory */
3250 artwork.gfx_current =
3251 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3252 if (artwork.gfx_current == NULL)
3253 artwork.gfx_current =
3254 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3255 if (artwork.gfx_current == NULL)
3256 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3258 artwork.snd_current =
3259 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3260 if (artwork.snd_current == NULL)
3261 artwork.snd_current =
3262 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3263 if (artwork.snd_current == NULL)
3264 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3266 artwork.mus_current =
3267 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3268 if (artwork.mus_current == NULL)
3269 artwork.mus_current =
3270 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3271 if (artwork.mus_current == NULL)
3272 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3274 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3275 artwork.snd_current_identifier = artwork.snd_current->identifier;
3276 artwork.mus_current_identifier = artwork.mus_current->identifier;
3278 #if ENABLE_UNUSED_CODE
3279 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3280 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3281 printf("music set == %s\n\n", artwork.mus_current_identifier);
3284 sortTreeInfo(&artwork.gfx_first);
3285 sortTreeInfo(&artwork.snd_first);
3286 sortTreeInfo(&artwork.mus_first);
3288 #if ENABLE_UNUSED_CODE
3289 dumpTreeInfo(artwork.gfx_first, 0);
3290 dumpTreeInfo(artwork.snd_first, 0);
3291 dumpTreeInfo(artwork.mus_first, 0);
3295 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3296 LevelDirTree *level_node)
3298 int type = (*artwork_node)->type;
3300 /* recursively check all level directories for artwork sub-directories */
3304 /* check all tree entries for artwork, but skip parent link entries */
3305 if (!level_node->parent_link)
3307 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3308 boolean cached = (artwork_new != NULL);
3312 pushTreeInfo(artwork_node, artwork_new);
3316 TreeInfo *topnode_last = *artwork_node;
3317 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3318 ARTWORK_DIRECTORY(type));
3320 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3322 if (topnode_last != *artwork_node) /* check for newly added node */
3324 artwork_new = *artwork_node;
3326 setString(&artwork_new->identifier, level_node->subdir);
3327 setString(&artwork_new->name, level_node->name);
3328 setString(&artwork_new->name_sorting, level_node->name_sorting);
3330 artwork_new->sort_priority = level_node->sort_priority;
3331 artwork_new->color = LEVELCOLOR(artwork_new);
3337 /* insert artwork info (from old cache or filesystem) into new cache */
3338 if (artwork_new != NULL)
3339 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3342 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3343 level_node->level_group);
3345 if (level_node->node_group != NULL)
3346 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3348 level_node = level_node->next;
3352 void LoadLevelArtworkInfo()
3354 print_timestamp_init("LoadLevelArtworkInfo");
3356 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3358 print_timestamp_time("DrawTimeText");
3360 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3361 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3362 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3363 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3364 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3365 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3367 SaveArtworkInfoCache();
3369 print_timestamp_time("SaveArtworkInfoCache");
3371 /* needed for reloading level artwork not known at ealier stage */
3373 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3375 artwork.gfx_current =
3376 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3377 if (artwork.gfx_current == NULL)
3378 artwork.gfx_current =
3379 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3380 if (artwork.gfx_current == NULL)
3381 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3384 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3386 artwork.snd_current =
3387 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3388 if (artwork.snd_current == NULL)
3389 artwork.snd_current =
3390 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3391 if (artwork.snd_current == NULL)
3392 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3395 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3397 artwork.mus_current =
3398 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3399 if (artwork.mus_current == NULL)
3400 artwork.mus_current =
3401 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3402 if (artwork.mus_current == NULL)
3403 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3406 print_timestamp_time("getTreeInfoFromIdentifier");
3408 sortTreeInfo(&artwork.gfx_first);
3409 sortTreeInfo(&artwork.snd_first);
3410 sortTreeInfo(&artwork.mus_first);
3412 print_timestamp_time("sortTreeInfo");
3414 #if ENABLE_UNUSED_CODE
3415 dumpTreeInfo(artwork.gfx_first, 0);
3416 dumpTreeInfo(artwork.snd_first, 0);
3417 dumpTreeInfo(artwork.mus_first, 0);
3420 print_timestamp_done("LoadLevelArtworkInfo");
3423 static void SaveUserLevelInfo()
3425 LevelDirTree *level_info;
3430 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3432 if (!(file = fopen(filename, MODE_WRITE)))
3434 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3439 level_info = newTreeInfo();
3441 /* always start with reliable default values */
3442 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3444 setString(&level_info->name, getLoginName());
3445 setString(&level_info->author, getRealName());
3446 level_info->levels = 100;
3447 level_info->first_level = 1;
3449 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3451 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3452 getCookie("LEVELINFO")));
3455 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3457 if (i == LEVELINFO_TOKEN_NAME ||
3458 i == LEVELINFO_TOKEN_AUTHOR ||
3459 i == LEVELINFO_TOKEN_LEVELS ||
3460 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3461 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3463 /* just to make things nicer :) */
3464 if (i == LEVELINFO_TOKEN_AUTHOR)
3465 fprintf(file, "\n");
3468 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3472 SetFilePermissions(filename, PERMS_PRIVATE);
3474 freeTreeInfo(level_info);
3478 char *getSetupValue(int type, void *value)
3480 static char value_string[MAX_LINE_LEN];
3488 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3492 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3496 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3497 *(int *)value == FALSE ? "off" : "on"));
3501 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3504 case TYPE_YES_NO_AUTO:
3505 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3506 *(int *)value == FALSE ? "no" : "yes"));
3510 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3514 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3518 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3522 sprintf(value_string, "%d", *(int *)value);
3526 if (*(char **)value == NULL)
3529 strcpy(value_string, *(char **)value);
3533 value_string[0] = '\0';
3537 if (type & TYPE_GHOSTED)
3538 strcpy(value_string, "n/a");
3540 return value_string;
3543 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3547 static char token_string[MAX_LINE_LEN];
3548 int token_type = token_info[token_nr].type;
3549 void *setup_value = token_info[token_nr].value;
3550 char *token_text = token_info[token_nr].text;
3551 char *value_string = getSetupValue(token_type, setup_value);
3553 /* build complete token string */
3554 sprintf(token_string, "%s%s", prefix, token_text);
3556 /* build setup entry line */
3557 line = getFormattedSetupEntry(token_string, value_string);
3559 if (token_type == TYPE_KEY_X11)
3561 Key key = *(Key *)setup_value;
3562 char *keyname = getKeyNameFromKey(key);
3564 /* add comment, if useful */
3565 if (!strEqual(keyname, "(undefined)") &&
3566 !strEqual(keyname, "(unknown)"))
3568 /* add at least one whitespace */
3570 for (i = strlen(line); i < token_comment_position; i++)
3574 strcat(line, keyname);
3581 void LoadLevelSetup_LastSeries()
3583 /* ----------------------------------------------------------------------- */
3584 /* ~/.<program>/levelsetup.conf */
3585 /* ----------------------------------------------------------------------- */
3587 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3588 SetupFileHash *level_setup_hash = NULL;
3590 /* always start with reliable default values */
3591 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3593 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3594 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3596 if (leveldir_current == NULL)
3597 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3600 if ((level_setup_hash = loadSetupFileHash(filename)))
3602 char *last_level_series =
3603 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3605 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3607 if (leveldir_current == NULL)
3608 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3610 checkSetupFileHashIdentifier(level_setup_hash, filename,
3611 getCookie("LEVELSETUP"));
3613 freeSetupFileHash(level_setup_hash);
3616 Error(ERR_WARN, "using default setup values");
3621 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3623 /* ----------------------------------------------------------------------- */
3624 /* ~/.<program>/levelsetup.conf */
3625 /* ----------------------------------------------------------------------- */
3627 // check if the current level directory structure is available at this point
3628 if (leveldir_current == NULL)
3631 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3632 char *level_subdir = leveldir_current->subdir;
3635 InitUserDataDirectory();
3637 if (!(file = fopen(filename, MODE_WRITE)))
3639 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3646 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3647 getCookie("LEVELSETUP")));
3649 if (deactivate_last_level_series)
3650 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3652 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3657 SetFilePermissions(filename, PERMS_PRIVATE);
3662 void SaveLevelSetup_LastSeries()
3664 SaveLevelSetup_LastSeries_Ext(FALSE);
3667 void SaveLevelSetup_LastSeries_Deactivate()
3669 SaveLevelSetup_LastSeries_Ext(TRUE);
3672 static void checkSeriesInfo()
3674 static char *level_directory = NULL;
3677 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3679 level_directory = getPath2((leveldir_current->in_user_dir ?
3680 getUserLevelDir(NULL) :
3681 options.level_directory),
3682 leveldir_current->fullpath);
3684 if ((dir = openDirectory(level_directory)) == NULL)
3686 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3691 closeDirectory(dir);
3694 void LoadLevelSetup_SeriesInfo()
3697 SetupFileHash *level_setup_hash = NULL;
3698 char *level_subdir = leveldir_current->subdir;
3701 /* always start with reliable default values */
3702 level_nr = leveldir_current->first_level;
3704 for (i = 0; i < MAX_LEVELS; i++)
3706 LevelStats_setPlayed(i, 0);
3707 LevelStats_setSolved(i, 0);
3710 checkSeriesInfo(leveldir_current);
3712 /* ----------------------------------------------------------------------- */
3713 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3714 /* ----------------------------------------------------------------------- */
3716 level_subdir = leveldir_current->subdir;
3718 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3720 if ((level_setup_hash = loadSetupFileHash(filename)))
3724 /* get last played level in this level set */
3726 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3730 level_nr = atoi(token_value);
3732 if (level_nr < leveldir_current->first_level)
3733 level_nr = leveldir_current->first_level;
3734 if (level_nr > leveldir_current->last_level)
3735 level_nr = leveldir_current->last_level;
3738 /* get handicap level in this level set */
3740 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3744 int level_nr = atoi(token_value);
3746 if (level_nr < leveldir_current->first_level)
3747 level_nr = leveldir_current->first_level;
3748 if (level_nr > leveldir_current->last_level + 1)
3749 level_nr = leveldir_current->last_level;
3751 if (leveldir_current->user_defined || !leveldir_current->handicap)
3752 level_nr = leveldir_current->last_level;
3754 leveldir_current->handicap_level = level_nr;
3757 /* get number of played and solved levels in this level set */
3759 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3761 char *token = HASH_ITERATION_TOKEN(itr);
3762 char *value = HASH_ITERATION_VALUE(itr);
3764 if (strlen(token) == 3 &&
3765 token[0] >= '0' && token[0] <= '9' &&
3766 token[1] >= '0' && token[1] <= '9' &&
3767 token[2] >= '0' && token[2] <= '9')
3769 int level_nr = atoi(token);
3772 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3774 value = strchr(value, ' ');
3777 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3780 END_HASH_ITERATION(hash, itr)
3782 checkSetupFileHashIdentifier(level_setup_hash, filename,
3783 getCookie("LEVELSETUP"));
3785 freeSetupFileHash(level_setup_hash);
3788 Error(ERR_WARN, "using default setup values");
3793 void SaveLevelSetup_SeriesInfo()
3796 char *level_subdir = leveldir_current->subdir;
3797 char *level_nr_str = int2str(level_nr, 0);
3798 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3802 /* ----------------------------------------------------------------------- */
3803 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3804 /* ----------------------------------------------------------------------- */
3806 InitLevelSetupDirectory(level_subdir);
3808 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3810 if (!(file = fopen(filename, MODE_WRITE)))
3812 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3817 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3818 getCookie("LEVELSETUP")));
3819 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3821 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3822 handicap_level_str));
3824 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3827 if (LevelStats_getPlayed(i) > 0 ||
3828 LevelStats_getSolved(i) > 0)
3833 sprintf(token, "%03d", i);
3834 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3836 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3842 SetFilePermissions(filename, PERMS_PRIVATE);
3847 int LevelStats_getPlayed(int nr)
3849 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3852 int LevelStats_getSolved(int nr)
3854 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3857 void LevelStats_setPlayed(int nr, int value)
3859 if (nr >= 0 && nr < MAX_LEVELS)
3860 level_stats[nr].played = value;
3863 void LevelStats_setSolved(int nr, int value)
3865 if (nr >= 0 && nr < MAX_LEVELS)
3866 level_stats[nr].solved = value;
3869 void LevelStats_incPlayed(int nr)
3871 if (nr >= 0 && nr < MAX_LEVELS)
3872 level_stats[nr].played++;
3875 void LevelStats_incSolved(int nr)
3877 if (nr >= 0 && nr < MAX_LEVELS)
3878 level_stats[nr].solved++;