1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
23 #if !defined(PLATFORM_WIN32)
25 #include <sys/param.h>
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 char *data_dir = getCommonDataDir();
137 char *score_subdir = SCORES_DIRECTORY;
139 checked_free(score_dir);
141 if (level_subdir != NULL)
142 score_dir = getPath3(data_dir, score_subdir, level_subdir);
144 score_dir = getPath2(data_dir, score_subdir);
149 static char *getLevelSetupDir(char *level_subdir)
151 static char *levelsetup_dir = NULL;
152 char *data_dir = getUserGameDataDir();
153 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
155 checked_free(levelsetup_dir);
157 if (level_subdir != NULL)
158 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
160 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
162 return levelsetup_dir;
165 static char *getCacheDir()
167 static char *cache_dir = NULL;
169 if (cache_dir == NULL)
170 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
175 static char *getLevelDirFromTreeInfo(TreeInfo *node)
177 static char *level_dir = NULL;
180 return options.level_directory;
182 checked_free(level_dir);
184 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
185 options.level_directory), node->fullpath);
190 char *getCurrentLevelDir()
192 return getLevelDirFromTreeInfo(leveldir_current);
195 static char *getTapeDir(char *level_subdir)
197 static char *tape_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *tape_subdir = TAPES_DIRECTORY;
201 checked_free(tape_dir);
203 if (level_subdir != NULL)
204 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
206 tape_dir = getPath2(data_dir, tape_subdir);
211 static char *getSolutionTapeDir()
213 static char *tape_dir = NULL;
214 char *data_dir = getCurrentLevelDir();
215 char *tape_subdir = TAPES_DIRECTORY;
217 checked_free(tape_dir);
219 tape_dir = getPath2(data_dir, tape_subdir);
224 static char *getDefaultGraphicsDir(char *graphics_subdir)
226 static char *graphics_dir = NULL;
228 if (graphics_subdir == NULL)
229 return options.graphics_directory;
231 checked_free(graphics_dir);
233 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
238 static char *getDefaultSoundsDir(char *sounds_subdir)
240 static char *sounds_dir = NULL;
242 if (sounds_subdir == NULL)
243 return options.sounds_directory;
245 checked_free(sounds_dir);
247 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
252 static char *getDefaultMusicDir(char *music_subdir)
254 static char *music_dir = NULL;
256 if (music_subdir == NULL)
257 return options.music_directory;
259 checked_free(music_dir);
261 music_dir = getPath2(options.music_directory, music_subdir);
266 static char *getClassicArtworkSet(int type)
268 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
269 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
270 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
273 static char *getClassicArtworkDir(int type)
275 return (type == TREE_TYPE_GRAPHICS_DIR ?
276 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_SOUNDS_DIR ?
278 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
279 type == TREE_TYPE_MUSIC_DIR ?
280 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
283 static char *getUserGraphicsDir()
285 static char *usergraphics_dir = NULL;
287 if (usergraphics_dir == NULL)
288 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
290 return usergraphics_dir;
293 static char *getUserSoundsDir()
295 static char *usersounds_dir = NULL;
297 if (usersounds_dir == NULL)
298 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
300 return usersounds_dir;
303 static char *getUserMusicDir()
305 static char *usermusic_dir = NULL;
307 if (usermusic_dir == NULL)
308 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
310 return usermusic_dir;
313 static char *getSetupArtworkDir(TreeInfo *ti)
315 static char *artwork_dir = NULL;
317 checked_free(artwork_dir);
319 artwork_dir = getPath2(ti->basepath, ti->fullpath);
324 char *setLevelArtworkDir(TreeInfo *ti)
326 char **artwork_path_ptr, **artwork_set_ptr;
327 TreeInfo *level_artwork;
329 if (ti == NULL || leveldir_current == NULL)
332 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
333 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
335 checked_free(*artwork_path_ptr);
337 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
339 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
344 No (or non-existing) artwork configured in "levelinfo.conf". This would
345 normally result in using the artwork configured in the setup menu. But
346 if an artwork subdirectory exists (which might contain custom artwork
347 or an artwork configuration file), this level artwork must be treated
348 as relative to the default "classic" artwork, not to the artwork that
349 is currently configured in the setup menu.
351 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
352 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
353 the real "classic" artwork from the original R'n'D (like "gfx_classic").
356 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
358 checked_free(*artwork_set_ptr);
360 if (directoryExists(dir))
362 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
363 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
367 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
368 *artwork_set_ptr = NULL;
374 return *artwork_set_ptr;
377 inline static char *getLevelArtworkSet(int type)
379 if (leveldir_current == NULL)
382 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
385 inline static char *getLevelArtworkDir(int type)
387 if (leveldir_current == NULL)
388 return UNDEFINED_FILENAME;
390 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
393 char *getTapeFilename(int nr)
395 static char *filename = NULL;
396 char basename[MAX_FILENAME_LEN];
398 checked_free(filename);
400 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
401 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
406 char *getSolutionTapeFilename(int nr)
408 static char *filename = NULL;
409 char basename[MAX_FILENAME_LEN];
411 checked_free(filename);
413 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
414 filename = getPath2(getSolutionTapeDir(), basename);
416 if (!fileExists(filename))
418 static char *filename_sln = NULL;
420 checked_free(filename_sln);
422 sprintf(basename, "%03d.sln", nr);
423 filename_sln = getPath2(getSolutionTapeDir(), basename);
425 if (fileExists(filename_sln))
432 char *getScoreFilename(int nr)
434 static char *filename = NULL;
435 char basename[MAX_FILENAME_LEN];
437 checked_free(filename);
439 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
440 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
445 char *getSetupFilename()
447 static char *filename = NULL;
449 checked_free(filename);
451 filename = getPath2(getSetupDir(), SETUP_FILENAME);
456 char *getEditorSetupFilename()
458 static char *filename = NULL;
460 checked_free(filename);
461 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
463 if (fileExists(filename))
466 checked_free(filename);
467 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
472 char *getHelpAnimFilename()
474 static char *filename = NULL;
476 checked_free(filename);
478 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
483 char *getHelpTextFilename()
485 static char *filename = NULL;
487 checked_free(filename);
489 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
494 char *getLevelSetInfoFilename()
496 static char *filename = NULL;
511 for (i = 0; basenames[i] != NULL; i++)
513 checked_free(filename);
514 filename = getPath2(getCurrentLevelDir(), basenames[i]);
516 if (fileExists(filename))
523 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
525 static char basename[32];
527 sprintf(basename, "%s_%d.txt",
528 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
533 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
535 static char *filename = NULL;
537 boolean skip_setup_artwork = FALSE;
539 checked_free(filename);
541 basename = getLevelSetTitleMessageBasename(nr, initial);
543 if (!gfx.override_level_graphics)
545 /* 1st try: look for special artwork in current level series directory */
546 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
547 if (fileExists(filename))
552 /* 2nd try: look for message file in current level set directory */
553 filename = getPath2(getCurrentLevelDir(), basename);
554 if (fileExists(filename))
559 /* check if there is special artwork configured in level series config */
560 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
562 /* 3rd try: look for special artwork configured in level series config */
563 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
564 if (fileExists(filename))
569 /* take missing artwork configured in level set config from default */
570 skip_setup_artwork = TRUE;
574 if (!skip_setup_artwork)
576 /* 4th try: look for special artwork in configured artwork directory */
577 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
578 if (fileExists(filename))
584 /* 5th try: look for default artwork in new default artwork directory */
585 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
586 if (fileExists(filename))
591 /* 6th try: look for default artwork in old default artwork directory */
592 filename = getPath2(options.graphics_directory, basename);
593 if (fileExists(filename))
596 return NULL; /* cannot find specified artwork file anywhere */
599 static char *getCorrectedArtworkBasename(char *basename)
601 char *basename_corrected = basename;
603 return basename_corrected;
606 char *getCustomImageFilename(char *basename)
608 static char *filename = NULL;
609 boolean skip_setup_artwork = FALSE;
611 checked_free(filename);
613 basename = getCorrectedArtworkBasename(basename);
615 if (!gfx.override_level_graphics)
617 /* 1st try: look for special artwork in current level series directory */
618 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
619 if (fileExists(filename))
624 /* check if there is special artwork configured in level series config */
625 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
627 /* 2nd try: look for special artwork configured in level series config */
628 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
629 if (fileExists(filename))
634 /* take missing artwork configured in level set config from default */
635 skip_setup_artwork = TRUE;
639 if (!skip_setup_artwork)
641 /* 3rd try: look for special artwork in configured artwork directory */
642 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
643 if (fileExists(filename))
649 /* 4th try: look for default artwork in new default artwork directory */
650 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
651 if (fileExists(filename))
656 /* 5th try: look for default artwork in old default artwork directory */
657 filename = getPath2(options.graphics_directory, basename);
658 if (fileExists(filename))
661 #if defined(CREATE_SPECIAL_EDITION)
665 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
667 /* 6th try: look for fallback artwork in old default artwork directory */
668 /* (needed to prevent errors when trying to access unused artwork files) */
669 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
670 if (fileExists(filename))
674 return NULL; /* cannot find specified artwork file anywhere */
677 char *getCustomSoundFilename(char *basename)
679 static char *filename = NULL;
680 boolean skip_setup_artwork = FALSE;
682 checked_free(filename);
684 basename = getCorrectedArtworkBasename(basename);
686 if (!gfx.override_level_sounds)
688 /* 1st try: look for special artwork in current level series directory */
689 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
690 if (fileExists(filename))
695 /* check if there is special artwork configured in level series config */
696 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
698 /* 2nd try: look for special artwork configured in level series config */
699 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
700 if (fileExists(filename))
705 /* take missing artwork configured in level set config from default */
706 skip_setup_artwork = TRUE;
710 if (!skip_setup_artwork)
712 /* 3rd try: look for special artwork in configured artwork directory */
713 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
714 if (fileExists(filename))
720 /* 4th try: look for default artwork in new default artwork directory */
721 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
722 if (fileExists(filename))
727 /* 5th try: look for default artwork in old default artwork directory */
728 filename = getPath2(options.sounds_directory, basename);
729 if (fileExists(filename))
732 #if defined(CREATE_SPECIAL_EDITION)
736 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
738 /* 6th try: look for fallback artwork in old default artwork directory */
739 /* (needed to prevent errors when trying to access unused artwork files) */
740 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
741 if (fileExists(filename))
745 return NULL; /* cannot find specified artwork file anywhere */
748 char *getCustomMusicFilename(char *basename)
750 static char *filename = NULL;
751 boolean skip_setup_artwork = FALSE;
753 checked_free(filename);
755 basename = getCorrectedArtworkBasename(basename);
757 if (!gfx.override_level_music)
759 /* 1st try: look for special artwork in current level series directory */
760 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
761 if (fileExists(filename))
766 /* check if there is special artwork configured in level series config */
767 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
769 /* 2nd try: look for special artwork configured in level series config */
770 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
771 if (fileExists(filename))
776 /* take missing artwork configured in level set config from default */
777 skip_setup_artwork = TRUE;
781 if (!skip_setup_artwork)
783 /* 3rd try: look for special artwork in configured artwork directory */
784 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
785 if (fileExists(filename))
791 /* 4th try: look for default artwork in new default artwork directory */
792 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
793 if (fileExists(filename))
798 /* 5th try: look for default artwork in old default artwork directory */
799 filename = getPath2(options.music_directory, basename);
800 if (fileExists(filename))
803 #if defined(CREATE_SPECIAL_EDITION)
807 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
809 /* 6th try: look for fallback artwork in old default artwork directory */
810 /* (needed to prevent errors when trying to access unused artwork files) */
811 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
812 if (fileExists(filename))
816 return NULL; /* cannot find specified artwork file anywhere */
819 char *getCustomArtworkFilename(char *basename, int type)
821 if (type == ARTWORK_TYPE_GRAPHICS)
822 return getCustomImageFilename(basename);
823 else if (type == ARTWORK_TYPE_SOUNDS)
824 return getCustomSoundFilename(basename);
825 else if (type == ARTWORK_TYPE_MUSIC)
826 return getCustomMusicFilename(basename);
828 return UNDEFINED_FILENAME;
831 char *getCustomArtworkConfigFilename(int type)
833 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
836 char *getCustomArtworkLevelConfigFilename(int type)
838 static char *filename = NULL;
840 checked_free(filename);
842 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
847 char *getCustomMusicDirectory(void)
849 static char *directory = NULL;
850 boolean skip_setup_artwork = FALSE;
852 checked_free(directory);
854 if (!gfx.override_level_music)
856 /* 1st try: look for special artwork in current level series directory */
857 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
858 if (directoryExists(directory))
863 /* check if there is special artwork configured in level series config */
864 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
866 /* 2nd try: look for special artwork configured in level series config */
867 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
868 if (directoryExists(directory))
873 /* take missing artwork configured in level set config from default */
874 skip_setup_artwork = TRUE;
878 if (!skip_setup_artwork)
880 /* 3rd try: look for special artwork in configured artwork directory */
881 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
882 if (directoryExists(directory))
888 /* 4th try: look for default artwork in new default artwork directory */
889 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
890 if (directoryExists(directory))
895 /* 5th try: look for default artwork in old default artwork directory */
896 directory = getStringCopy(options.music_directory);
897 if (directoryExists(directory))
900 return NULL; /* cannot find specified artwork file anywhere */
903 void InitTapeDirectory(char *level_subdir)
905 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
906 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
907 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
910 void InitScoreDirectory(char *level_subdir)
912 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
913 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
914 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
917 static void SaveUserLevelInfo();
919 void InitUserLevelDirectory(char *level_subdir)
921 if (!directoryExists(getUserLevelDir(level_subdir)))
923 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
924 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
925 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
931 void InitLevelSetupDirectory(char *level_subdir)
933 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
934 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
935 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
938 void InitCacheDirectory()
940 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
941 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
945 /* ------------------------------------------------------------------------- */
946 /* some functions to handle lists of level and artwork directories */
947 /* ------------------------------------------------------------------------- */
949 TreeInfo *newTreeInfo()
951 return checked_calloc(sizeof(TreeInfo));
954 TreeInfo *newTreeInfo_setDefaults(int type)
956 TreeInfo *ti = newTreeInfo();
958 setTreeInfoToDefaults(ti, type);
963 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
965 node_new->next = *node_first;
966 *node_first = node_new;
969 int numTreeInfo(TreeInfo *node)
982 boolean validLevelSeries(TreeInfo *node)
984 return (node != NULL && !node->node_group && !node->parent_link);
987 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
992 if (node->node_group) /* enter level group (step down into tree) */
993 return getFirstValidTreeInfoEntry(node->node_group);
994 else if (node->parent_link) /* skip start entry of level group */
996 if (node->next) /* get first real level series entry */
997 return getFirstValidTreeInfoEntry(node->next);
998 else /* leave empty level group and go on */
999 return getFirstValidTreeInfoEntry(node->node_parent->next);
1001 else /* this seems to be a regular level series */
1005 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1010 if (node->node_parent == NULL) /* top level group */
1011 return *node->node_top;
1012 else /* sub level group */
1013 return node->node_parent->node_group;
1016 int numTreeInfoInGroup(TreeInfo *node)
1018 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1021 int posTreeInfo(TreeInfo *node)
1023 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1028 if (node_cmp == node)
1032 node_cmp = node_cmp->next;
1038 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1040 TreeInfo *node_default = node;
1052 return node_default;
1055 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1057 if (identifier == NULL)
1062 if (node->node_group)
1064 TreeInfo *node_group;
1066 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1071 else if (!node->parent_link)
1073 if (strEqual(identifier, node->identifier))
1083 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1084 TreeInfo *node, boolean skip_sets_without_levels)
1091 if (!node->parent_link && !node->level_group &&
1092 skip_sets_without_levels && node->levels == 0)
1093 return cloneTreeNode(node_top, node_parent, node->next,
1094 skip_sets_without_levels);
1097 node_new = getTreeInfoCopy(node); /* copy complete node */
1099 node_new = newTreeInfo();
1101 *node_new = *node; /* copy complete node */
1104 node_new->node_top = node_top; /* correct top node link */
1105 node_new->node_parent = node_parent; /* correct parent node link */
1107 if (node->level_group)
1108 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1109 skip_sets_without_levels);
1111 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1112 skip_sets_without_levels);
1117 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1119 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1121 *ti_new = ti_cloned;
1124 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1126 boolean settings_changed = FALSE;
1130 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1131 !strEqual(node->graphics_set, node->graphics_set_ecs))
1133 setString(&node->graphics_set, node->graphics_set_ecs);
1134 settings_changed = TRUE;
1136 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1137 !strEqual(node->graphics_set, node->graphics_set_aga))
1139 setString(&node->graphics_set, node->graphics_set_aga);
1140 settings_changed = TRUE;
1143 if (node->node_group != NULL)
1144 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1149 return settings_changed;
1152 void dumpTreeInfo(TreeInfo *node, int depth)
1156 printf("Dumping TreeInfo:\n");
1160 for (i = 0; i < (depth + 1) * 3; i++)
1163 printf("'%s' / '%s'\n", node->identifier, node->name);
1166 // use for dumping artwork info tree
1167 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1168 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1171 if (node->node_group != NULL)
1172 dumpTreeInfo(node->node_group, depth + 1);
1178 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1179 int (*compare_function)(const void *,
1182 int num_nodes = numTreeInfo(*node_first);
1183 TreeInfo **sort_array;
1184 TreeInfo *node = *node_first;
1190 /* allocate array for sorting structure pointers */
1191 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1193 /* writing structure pointers to sorting array */
1194 while (i < num_nodes && node) /* double boundary check... */
1196 sort_array[i] = node;
1202 /* sorting the structure pointers in the sorting array */
1203 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1206 /* update the linkage of list elements with the sorted node array */
1207 for (i = 0; i < num_nodes - 1; i++)
1208 sort_array[i]->next = sort_array[i + 1];
1209 sort_array[num_nodes - 1]->next = NULL;
1211 /* update the linkage of the main list anchor pointer */
1212 *node_first = sort_array[0];
1216 /* now recursively sort the level group structures */
1220 if (node->node_group != NULL)
1221 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1227 void sortTreeInfo(TreeInfo **node_first)
1229 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1233 /* ========================================================================= */
1234 /* some stuff from "files.c" */
1235 /* ========================================================================= */
1237 #if defined(PLATFORM_WIN32)
1239 #define S_IRGRP S_IRUSR
1242 #define S_IROTH S_IRUSR
1245 #define S_IWGRP S_IWUSR
1248 #define S_IWOTH S_IWUSR
1251 #define S_IXGRP S_IXUSR
1254 #define S_IXOTH S_IXUSR
1257 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1262 #endif /* PLATFORM_WIN32 */
1264 /* file permissions for newly written files */
1265 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1266 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1267 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1269 #define MODE_W_PRIVATE (S_IWUSR)
1270 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1271 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1273 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1274 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1276 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1277 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1281 static char *dir = NULL;
1283 #if defined(PLATFORM_WIN32)
1286 dir = checked_malloc(MAX_PATH + 1);
1288 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1291 #elif defined(PLATFORM_UNIX)
1294 if ((dir = getenv("HOME")) == NULL)
1298 if ((pwd = getpwuid(getuid())) != NULL)
1299 dir = getStringCopy(pwd->pw_dir);
1311 char *getCommonDataDir(void)
1313 static char *common_data_dir = NULL;
1315 #if defined(PLATFORM_WIN32)
1316 if (common_data_dir == NULL)
1318 char *dir = checked_malloc(MAX_PATH + 1);
1320 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1321 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1322 common_data_dir = getPath2(dir, program.userdata_subdir);
1324 common_data_dir = options.rw_base_directory;
1327 if (common_data_dir == NULL)
1328 common_data_dir = options.rw_base_directory;
1331 return common_data_dir;
1334 char *getPersonalDataDir(void)
1336 static char *personal_data_dir = NULL;
1338 #if defined(PLATFORM_MACOSX)
1339 if (personal_data_dir == NULL)
1340 personal_data_dir = getPath2(getHomeDir(), "Documents");
1342 if (personal_data_dir == NULL)
1343 personal_data_dir = getHomeDir();
1346 return personal_data_dir;
1349 char *getUserGameDataDir(void)
1351 static char *user_game_data_dir = NULL;
1353 #if defined(PLATFORM_ANDROID)
1354 if (user_game_data_dir == NULL)
1355 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1357 if (user_game_data_dir == NULL)
1358 user_game_data_dir = getPath2(getPersonalDataDir(),
1359 program.userdata_subdir);
1362 return user_game_data_dir;
1365 void updateUserGameDataDir()
1367 #if defined(PLATFORM_MACOSX)
1368 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1369 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1371 /* convert old Unix style game data directory to Mac OS X style, if needed */
1372 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1374 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1376 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1377 userdata_dir_old, userdata_dir_new);
1379 /* continue using Unix style data directory -- this should not happen */
1380 program.userdata_path = getPath2(getPersonalDataDir(),
1381 program.userdata_subdir_unix);
1385 free(userdata_dir_old);
1391 return getUserGameDataDir();
1394 static mode_t posix_umask(mode_t mask)
1396 #if defined(PLATFORM_UNIX)
1403 static int posix_mkdir(const char *pathname, mode_t mode)
1405 #if defined(PLATFORM_WIN32)
1406 return mkdir(pathname);
1408 return mkdir(pathname, mode);
1412 static boolean posix_process_running_setgid()
1414 #if defined(PLATFORM_UNIX)
1415 return (getgid() != getegid());
1421 void createDirectory(char *dir, char *text, int permission_class)
1423 /* leave "other" permissions in umask untouched, but ensure group parts
1424 of USERDATA_DIR_MODE are not masked */
1425 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1426 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1427 mode_t last_umask = posix_umask(0);
1428 mode_t group_umask = ~(dir_mode & S_IRWXG);
1429 int running_setgid = posix_process_running_setgid();
1431 /* if we're setgid, protect files against "other" */
1432 /* else keep umask(0) to make the dir world-writable */
1435 posix_umask(last_umask & group_umask);
1437 dir_mode |= MODE_W_ALL;
1439 if (!directoryExists(dir))
1440 if (posix_mkdir(dir, dir_mode) != 0)
1441 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1442 text, dir, strerror(errno));
1444 if (permission_class == PERMS_PUBLIC && !running_setgid)
1445 chmod(dir, dir_mode);
1447 posix_umask(last_umask); /* restore previous umask */
1450 void InitUserDataDirectory()
1452 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1455 void SetFilePermissions(char *filename, int permission_class)
1457 int running_setgid = posix_process_running_setgid();
1458 int perms = (permission_class == PERMS_PRIVATE ?
1459 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1461 if (permission_class == PERMS_PUBLIC && !running_setgid)
1462 perms |= MODE_W_ALL;
1464 chmod(filename, perms);
1467 char *getCookie(char *file_type)
1469 static char cookie[MAX_COOKIE_LEN + 1];
1471 if (strlen(program.cookie_prefix) + 1 +
1472 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1473 return "[COOKIE ERROR]"; /* should never happen */
1475 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1476 program.cookie_prefix, file_type,
1477 program.version_major, program.version_minor);
1482 int getFileVersionFromCookieString(const char *cookie)
1484 const char *ptr_cookie1, *ptr_cookie2;
1485 const char *pattern1 = "_FILE_VERSION_";
1486 const char *pattern2 = "?.?";
1487 const int len_cookie = strlen(cookie);
1488 const int len_pattern1 = strlen(pattern1);
1489 const int len_pattern2 = strlen(pattern2);
1490 const int len_pattern = len_pattern1 + len_pattern2;
1491 int version_major, version_minor;
1493 if (len_cookie <= len_pattern)
1496 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1497 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1499 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1502 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1503 ptr_cookie2[1] != '.' ||
1504 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1507 version_major = ptr_cookie2[0] - '0';
1508 version_minor = ptr_cookie2[2] - '0';
1510 return VERSION_IDENT(version_major, version_minor, 0, 0);
1513 boolean checkCookieString(const char *cookie, const char *template)
1515 const char *pattern = "_FILE_VERSION_?.?";
1516 const int len_cookie = strlen(cookie);
1517 const int len_template = strlen(template);
1518 const int len_pattern = strlen(pattern);
1520 if (len_cookie != len_template)
1523 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1529 /* ------------------------------------------------------------------------- */
1530 /* setup file list and hash handling functions */
1531 /* ------------------------------------------------------------------------- */
1533 char *getFormattedSetupEntry(char *token, char *value)
1536 static char entry[MAX_LINE_LEN];
1538 /* if value is an empty string, just return token without value */
1542 /* start with the token and some spaces to format output line */
1543 sprintf(entry, "%s:", token);
1544 for (i = strlen(entry); i < token_value_position; i++)
1547 /* continue with the token's value */
1548 strcat(entry, value);
1553 SetupFileList *newSetupFileList(char *token, char *value)
1555 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1557 new->token = getStringCopy(token);
1558 new->value = getStringCopy(value);
1565 void freeSetupFileList(SetupFileList *list)
1570 checked_free(list->token);
1571 checked_free(list->value);
1574 freeSetupFileList(list->next);
1579 char *getListEntry(SetupFileList *list, char *token)
1584 if (strEqual(list->token, token))
1587 return getListEntry(list->next, token);
1590 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1595 if (strEqual(list->token, token))
1597 checked_free(list->value);
1599 list->value = getStringCopy(value);
1603 else if (list->next == NULL)
1604 return (list->next = newSetupFileList(token, value));
1606 return setListEntry(list->next, token, value);
1609 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1614 if (list->next == NULL)
1615 return (list->next = newSetupFileList(token, value));
1617 return addListEntry(list->next, token, value);
1622 static void printSetupFileList(SetupFileList *list)
1627 printf("token: '%s'\n", list->token);
1628 printf("value: '%s'\n", list->value);
1630 printSetupFileList(list->next);
1636 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1637 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1638 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1639 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1641 #define insert_hash_entry hashtable_insert
1642 #define search_hash_entry hashtable_search
1643 #define change_hash_entry hashtable_change
1644 #define remove_hash_entry hashtable_remove
1647 unsigned int get_hash_from_key(void *key)
1652 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1653 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1654 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1655 it works better than many other constants, prime or not) has never been
1656 adequately explained.
1658 If you just want to have a good hash function, and cannot wait, djb2
1659 is one of the best string hash functions i know. It has excellent
1660 distribution and speed on many different sets of keys and table sizes.
1661 You are not likely to do better with one of the "well known" functions
1662 such as PJW, K&R, etc.
1664 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1667 char *str = (char *)key;
1668 unsigned int hash = 5381;
1671 while ((c = *str++))
1672 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1677 static int keys_are_equal(void *key1, void *key2)
1679 return (strEqual((char *)key1, (char *)key2));
1682 SetupFileHash *newSetupFileHash()
1684 SetupFileHash *new_hash =
1685 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1687 if (new_hash == NULL)
1688 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1693 void freeSetupFileHash(SetupFileHash *hash)
1698 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1701 char *getHashEntry(SetupFileHash *hash, char *token)
1706 return search_hash_entry(hash, token);
1709 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1716 value_copy = getStringCopy(value);
1718 /* change value; if it does not exist, insert it as new */
1719 if (!change_hash_entry(hash, token, value_copy))
1720 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1721 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1724 char *removeHashEntry(SetupFileHash *hash, char *token)
1729 return remove_hash_entry(hash, token);
1733 static void printSetupFileHash(SetupFileHash *hash)
1735 BEGIN_HASH_ITERATION(hash, itr)
1737 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1738 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1740 END_HASH_ITERATION(hash, itr)
1744 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1745 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1746 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1748 static boolean token_value_separator_found = FALSE;
1749 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1750 static boolean token_value_separator_warning = FALSE;
1752 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1753 static boolean token_already_exists_warning = FALSE;
1756 static boolean getTokenValueFromSetupLineExt(char *line,
1757 char **token_ptr, char **value_ptr,
1758 char *filename, char *line_raw,
1760 boolean separator_required)
1762 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1763 char *token, *value, *line_ptr;
1765 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1766 if (line_raw == NULL)
1768 strncpy(line_copy, line, MAX_LINE_LEN);
1769 line_copy[MAX_LINE_LEN] = '\0';
1772 strcpy(line_raw_copy, line_copy);
1773 line_raw = line_raw_copy;
1776 /* cut trailing comment from input line */
1777 for (line_ptr = line; *line_ptr; line_ptr++)
1779 if (*line_ptr == '#')
1786 /* cut trailing whitespaces from input line */
1787 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1788 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1791 /* ignore empty lines */
1795 /* cut leading whitespaces from token */
1796 for (token = line; *token; token++)
1797 if (*token != ' ' && *token != '\t')
1800 /* start with empty value as reliable default */
1803 token_value_separator_found = FALSE;
1805 /* find end of token to determine start of value */
1806 for (line_ptr = token; *line_ptr; line_ptr++)
1809 /* first look for an explicit token/value separator, like ':' or '=' */
1810 if (*line_ptr == ':' || *line_ptr == '=')
1812 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1815 *line_ptr = '\0'; /* terminate token string */
1816 value = line_ptr + 1; /* set beginning of value */
1818 token_value_separator_found = TRUE;
1824 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1825 /* fallback: if no token/value separator found, also allow whitespaces */
1826 if (!token_value_separator_found && !separator_required)
1828 for (line_ptr = token; *line_ptr; line_ptr++)
1830 if (*line_ptr == ' ' || *line_ptr == '\t')
1832 *line_ptr = '\0'; /* terminate token string */
1833 value = line_ptr + 1; /* set beginning of value */
1835 token_value_separator_found = TRUE;
1841 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1842 if (token_value_separator_found)
1844 if (!token_value_separator_warning)
1846 Error(ERR_INFO_LINE, "-");
1848 if (filename != NULL)
1850 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1851 Error(ERR_INFO, "- config file: '%s'", filename);
1855 Error(ERR_WARN, "missing token/value separator(s):");
1858 token_value_separator_warning = TRUE;
1861 if (filename != NULL)
1862 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1864 Error(ERR_INFO, "- line: '%s'", line_raw);
1870 /* cut trailing whitespaces from token */
1871 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1872 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1875 /* cut leading whitespaces from value */
1876 for (; *value; value++)
1877 if (*value != ' ' && *value != '\t')
1882 value = "true"; /* treat tokens without value as "true" */
1891 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1893 /* while the internal (old) interface does not require a token/value
1894 separator (for downwards compatibility with existing files which
1895 don't use them), it is mandatory for the external (new) interface */
1897 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1903 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1904 boolean top_recursion_level, boolean is_hash)
1906 static SetupFileHash *include_filename_hash = NULL;
1907 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1908 char *token, *value, *line_ptr;
1909 void *insert_ptr = NULL;
1910 boolean read_continued_line = FALSE;
1912 int line_nr = 0, token_count = 0, include_count = 0;
1914 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1915 token_value_separator_warning = FALSE;
1918 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1919 token_already_exists_warning = FALSE;
1923 Error(ERR_INFO, "===== opening file: '%s'", filename);
1926 if (!(file = openFile(filename, MODE_READ)))
1928 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1934 Error(ERR_INFO, "===== reading file: '%s'", filename);
1937 /* use "insert pointer" to store list end for constant insertion complexity */
1939 insert_ptr = setup_file_data;
1941 /* on top invocation, create hash to mark included files (to prevent loops) */
1942 if (top_recursion_level)
1943 include_filename_hash = newSetupFileHash();
1945 /* mark this file as already included (to prevent including it again) */
1946 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1948 while (!checkEndOfFile(file))
1950 /* read next line of input file */
1951 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1955 Error(ERR_INFO, "got line: '%s'", line);
1958 /* check if line was completely read and is terminated by line break */
1959 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1962 /* cut trailing line break (this can be newline and/or carriage return) */
1963 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1964 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1967 /* copy raw input line for later use (mainly debugging output) */
1968 strcpy(line_raw, line);
1970 if (read_continued_line)
1973 /* !!! ??? WHY ??? !!! */
1974 /* cut leading whitespaces from input line */
1975 for (line_ptr = line; *line_ptr; line_ptr++)
1976 if (*line_ptr != ' ' && *line_ptr != '\t')
1980 /* append new line to existing line, if there is enough space */
1981 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1982 strcat(previous_line, line_ptr);
1984 strcpy(line, previous_line); /* copy storage buffer to line */
1986 read_continued_line = FALSE;
1989 /* if the last character is '\', continue at next line */
1990 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1992 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1993 strcpy(previous_line, line); /* copy line to storage buffer */
1995 read_continued_line = TRUE;
2000 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2001 line_raw, line_nr, FALSE))
2006 if (strEqual(token, "include"))
2008 if (getHashEntry(include_filename_hash, value) == NULL)
2010 char *basepath = getBasePath(filename);
2011 char *basename = getBaseName(value);
2012 char *filename_include = getPath2(basepath, basename);
2015 Error(ERR_INFO, "[including file '%s']", filename_include);
2018 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2022 free(filename_include);
2028 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2035 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2037 getHashEntry((SetupFileHash *)setup_file_data, token);
2039 if (old_value != NULL)
2041 if (!token_already_exists_warning)
2043 Error(ERR_INFO_LINE, "-");
2044 Error(ERR_WARN, "duplicate token(s) found in config file:");
2045 Error(ERR_INFO, "- config file: '%s'", filename);
2047 token_already_exists_warning = TRUE;
2050 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2051 Error(ERR_INFO, " old value: '%s'", old_value);
2052 Error(ERR_INFO, " new value: '%s'", value);
2056 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2060 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2070 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2071 if (token_value_separator_warning)
2072 Error(ERR_INFO_LINE, "-");
2075 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2076 if (token_already_exists_warning)
2077 Error(ERR_INFO_LINE, "-");
2080 if (token_count == 0 && include_count == 0)
2081 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2083 if (top_recursion_level)
2084 freeSetupFileHash(include_filename_hash);
2091 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2092 boolean top_recursion_level, boolean is_hash)
2094 static SetupFileHash *include_filename_hash = NULL;
2095 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2096 char *token, *value, *line_ptr;
2097 void *insert_ptr = NULL;
2098 boolean read_continued_line = FALSE;
2100 int line_nr = 0, token_count = 0, include_count = 0;
2102 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2103 token_value_separator_warning = FALSE;
2106 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2107 token_already_exists_warning = FALSE;
2110 if (!(file = fopen(filename, MODE_READ)))
2112 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2117 /* use "insert pointer" to store list end for constant insertion complexity */
2119 insert_ptr = setup_file_data;
2121 /* on top invocation, create hash to mark included files (to prevent loops) */
2122 if (top_recursion_level)
2123 include_filename_hash = newSetupFileHash();
2125 /* mark this file as already included (to prevent including it again) */
2126 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2130 /* read next line of input file */
2131 if (!fgets(line, MAX_LINE_LEN, file))
2134 /* check if line was completely read and is terminated by line break */
2135 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2138 /* cut trailing line break (this can be newline and/or carriage return) */
2139 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2140 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2143 /* copy raw input line for later use (mainly debugging output) */
2144 strcpy(line_raw, line);
2146 if (read_continued_line)
2149 /* !!! ??? WHY ??? !!! */
2150 /* cut leading whitespaces from input line */
2151 for (line_ptr = line; *line_ptr; line_ptr++)
2152 if (*line_ptr != ' ' && *line_ptr != '\t')
2156 /* append new line to existing line, if there is enough space */
2157 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2158 strcat(previous_line, line_ptr);
2160 strcpy(line, previous_line); /* copy storage buffer to line */
2162 read_continued_line = FALSE;
2165 /* if the last character is '\', continue at next line */
2166 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2168 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2169 strcpy(previous_line, line); /* copy line to storage buffer */
2171 read_continued_line = TRUE;
2176 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2177 line_raw, line_nr, FALSE))
2182 if (strEqual(token, "include"))
2184 if (getHashEntry(include_filename_hash, value) == NULL)
2186 char *basepath = getBasePath(filename);
2187 char *basename = getBaseName(value);
2188 char *filename_include = getPath2(basepath, basename);
2191 Error(ERR_INFO, "[including file '%s']", filename_include);
2194 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2198 free(filename_include);
2204 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2211 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2213 getHashEntry((SetupFileHash *)setup_file_data, token);
2215 if (old_value != NULL)
2217 if (!token_already_exists_warning)
2219 Error(ERR_INFO_LINE, "-");
2220 Error(ERR_WARN, "duplicate token(s) found in config file:");
2221 Error(ERR_INFO, "- config file: '%s'", filename);
2223 token_already_exists_warning = TRUE;
2226 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2227 Error(ERR_INFO, " old value: '%s'", old_value);
2228 Error(ERR_INFO, " new value: '%s'", value);
2232 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2236 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2246 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2247 if (token_value_separator_warning)
2248 Error(ERR_INFO_LINE, "-");
2251 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2252 if (token_already_exists_warning)
2253 Error(ERR_INFO_LINE, "-");
2256 if (token_count == 0 && include_count == 0)
2257 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2259 if (top_recursion_level)
2260 freeSetupFileHash(include_filename_hash);
2269 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2270 boolean top_recursion_level, boolean is_hash)
2272 static SetupFileHash *include_filename_hash = NULL;
2273 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2274 char *token, *value, *line_ptr;
2275 void *insert_ptr = NULL;
2276 boolean read_continued_line = FALSE;
2279 int token_count = 0;
2281 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2282 token_value_separator_warning = FALSE;
2285 if (!(file = fopen(filename, MODE_READ)))
2287 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2292 /* use "insert pointer" to store list end for constant insertion complexity */
2294 insert_ptr = setup_file_data;
2296 /* on top invocation, create hash to mark included files (to prevent loops) */
2297 if (top_recursion_level)
2298 include_filename_hash = newSetupFileHash();
2300 /* mark this file as already included (to prevent including it again) */
2301 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2305 /* read next line of input file */
2306 if (!fgets(line, MAX_LINE_LEN, file))
2309 /* check if line was completely read and is terminated by line break */
2310 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2313 /* cut trailing line break (this can be newline and/or carriage return) */
2314 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2315 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2318 /* copy raw input line for later use (mainly debugging output) */
2319 strcpy(line_raw, line);
2321 if (read_continued_line)
2323 /* cut leading whitespaces from input line */
2324 for (line_ptr = line; *line_ptr; line_ptr++)
2325 if (*line_ptr != ' ' && *line_ptr != '\t')
2328 /* append new line to existing line, if there is enough space */
2329 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2330 strcat(previous_line, line_ptr);
2332 strcpy(line, previous_line); /* copy storage buffer to line */
2334 read_continued_line = FALSE;
2337 /* if the last character is '\', continue at next line */
2338 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2340 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2341 strcpy(previous_line, line); /* copy line to storage buffer */
2343 read_continued_line = TRUE;
2348 /* cut trailing comment from input line */
2349 for (line_ptr = line; *line_ptr; line_ptr++)
2351 if (*line_ptr == '#')
2358 /* cut trailing whitespaces from input line */
2359 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2360 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2363 /* ignore empty lines */
2367 /* cut leading whitespaces from token */
2368 for (token = line; *token; token++)
2369 if (*token != ' ' && *token != '\t')
2372 /* start with empty value as reliable default */
2375 token_value_separator_found = FALSE;
2377 /* find end of token to determine start of value */
2378 for (line_ptr = token; *line_ptr; line_ptr++)
2381 /* first look for an explicit token/value separator, like ':' or '=' */
2382 if (*line_ptr == ':' || *line_ptr == '=')
2384 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2387 *line_ptr = '\0'; /* terminate token string */
2388 value = line_ptr + 1; /* set beginning of value */
2390 token_value_separator_found = TRUE;
2396 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2397 /* fallback: if no token/value separator found, also allow whitespaces */
2398 if (!token_value_separator_found)
2400 for (line_ptr = token; *line_ptr; line_ptr++)
2402 if (*line_ptr == ' ' || *line_ptr == '\t')
2404 *line_ptr = '\0'; /* terminate token string */
2405 value = line_ptr + 1; /* set beginning of value */
2407 token_value_separator_found = TRUE;
2413 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2414 if (token_value_separator_found)
2416 if (!token_value_separator_warning)
2418 Error(ERR_INFO_LINE, "-");
2419 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2420 Error(ERR_INFO, "- config file: '%s'", filename);
2422 token_value_separator_warning = TRUE;
2425 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2431 /* cut trailing whitespaces from token */
2432 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2433 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2436 /* cut leading whitespaces from value */
2437 for (; *value; value++)
2438 if (*value != ' ' && *value != '\t')
2443 value = "true"; /* treat tokens without value as "true" */
2448 if (strEqual(token, "include"))
2450 if (getHashEntry(include_filename_hash, value) == NULL)
2452 char *basepath = getBasePath(filename);
2453 char *basename = getBaseName(value);
2454 char *filename_include = getPath2(basepath, basename);
2457 Error(ERR_INFO, "[including file '%s']", filename_include);
2460 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2464 free(filename_include);
2468 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2474 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2476 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2485 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2486 if (token_value_separator_warning)
2487 Error(ERR_INFO_LINE, "-");
2490 if (token_count == 0)
2491 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2493 if (top_recursion_level)
2494 freeSetupFileHash(include_filename_hash);
2500 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2504 if (!(file = fopen(filename, MODE_WRITE)))
2506 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2511 BEGIN_HASH_ITERATION(hash, itr)
2513 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2514 HASH_ITERATION_VALUE(itr)));
2516 END_HASH_ITERATION(hash, itr)
2521 SetupFileList *loadSetupFileList(char *filename)
2523 SetupFileList *setup_file_list = newSetupFileList("", "");
2524 SetupFileList *first_valid_list_entry;
2526 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2528 freeSetupFileList(setup_file_list);
2533 first_valid_list_entry = setup_file_list->next;
2535 /* free empty list header */
2536 setup_file_list->next = NULL;
2537 freeSetupFileList(setup_file_list);
2539 return first_valid_list_entry;
2542 SetupFileHash *loadSetupFileHash(char *filename)
2544 SetupFileHash *setup_file_hash = newSetupFileHash();
2546 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2548 freeSetupFileHash(setup_file_hash);
2553 return setup_file_hash;
2556 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2557 char *filename, char *identifier)
2559 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2562 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2563 else if (!checkCookieString(value, identifier))
2564 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2568 /* ========================================================================= */
2569 /* setup file stuff */
2570 /* ========================================================================= */
2572 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2573 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2574 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2576 /* level directory info */
2577 #define LEVELINFO_TOKEN_IDENTIFIER 0
2578 #define LEVELINFO_TOKEN_NAME 1
2579 #define LEVELINFO_TOKEN_NAME_SORTING 2
2580 #define LEVELINFO_TOKEN_AUTHOR 3
2581 #define LEVELINFO_TOKEN_YEAR 4
2582 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2583 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2584 #define LEVELINFO_TOKEN_TESTED_BY 7
2585 #define LEVELINFO_TOKEN_LEVELS 8
2586 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2587 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2588 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2589 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2590 #define LEVELINFO_TOKEN_READONLY 13
2591 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2592 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2593 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2594 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2595 #define LEVELINFO_TOKEN_MUSIC_SET 18
2596 #define LEVELINFO_TOKEN_FILENAME 19
2597 #define LEVELINFO_TOKEN_FILETYPE 20
2598 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2599 #define LEVELINFO_TOKEN_HANDICAP 22
2600 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2602 #define NUM_LEVELINFO_TOKENS 24
2604 static LevelDirTree ldi;
2606 static struct TokenInfo levelinfo_tokens[] =
2608 /* level directory info */
2609 { TYPE_STRING, &ldi.identifier, "identifier" },
2610 { TYPE_STRING, &ldi.name, "name" },
2611 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2612 { TYPE_STRING, &ldi.author, "author" },
2613 { TYPE_STRING, &ldi.year, "year" },
2614 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2615 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2616 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2617 { TYPE_INTEGER, &ldi.levels, "levels" },
2618 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2619 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2620 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2621 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2622 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2623 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2624 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2625 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2626 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2627 { TYPE_STRING, &ldi.music_set, "music_set" },
2628 { TYPE_STRING, &ldi.level_filename, "filename" },
2629 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2630 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2631 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2632 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2635 static struct TokenInfo artworkinfo_tokens[] =
2637 /* artwork directory info */
2638 { TYPE_STRING, &ldi.identifier, "identifier" },
2639 { TYPE_STRING, &ldi.subdir, "subdir" },
2640 { TYPE_STRING, &ldi.name, "name" },
2641 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2642 { TYPE_STRING, &ldi.author, "author" },
2643 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2644 { TYPE_STRING, &ldi.basepath, "basepath" },
2645 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2646 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2647 { TYPE_INTEGER, &ldi.color, "color" },
2648 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2653 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2657 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2658 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2659 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2660 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2663 ti->node_parent = NULL;
2664 ti->node_group = NULL;
2671 ti->fullpath = NULL;
2672 ti->basepath = NULL;
2673 ti->identifier = NULL;
2674 ti->name = getStringCopy(ANONYMOUS_NAME);
2675 ti->name_sorting = NULL;
2676 ti->author = getStringCopy(ANONYMOUS_NAME);
2679 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2680 ti->latest_engine = FALSE; /* default: get from level */
2681 ti->parent_link = FALSE;
2682 ti->in_user_dir = FALSE;
2683 ti->user_defined = FALSE;
2685 ti->class_desc = NULL;
2687 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2689 if (ti->type == TREE_TYPE_LEVEL_DIR)
2691 ti->imported_from = NULL;
2692 ti->imported_by = NULL;
2693 ti->tested_by = NULL;
2695 ti->graphics_set_ecs = NULL;
2696 ti->graphics_set_aga = NULL;
2697 ti->graphics_set = NULL;
2698 ti->sounds_set = NULL;
2699 ti->music_set = NULL;
2700 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2701 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2702 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2704 ti->level_filename = NULL;
2705 ti->level_filetype = NULL;
2707 ti->special_flags = NULL;
2710 ti->first_level = 0;
2712 ti->level_group = FALSE;
2713 ti->handicap_level = 0;
2714 ti->readonly = TRUE;
2715 ti->handicap = TRUE;
2716 ti->skip_levels = FALSE;
2720 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2724 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2726 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2731 /* copy all values from the parent structure */
2733 ti->type = parent->type;
2735 ti->node_top = parent->node_top;
2736 ti->node_parent = parent;
2737 ti->node_group = NULL;
2744 ti->fullpath = NULL;
2745 ti->basepath = NULL;
2746 ti->identifier = NULL;
2747 ti->name = getStringCopy(ANONYMOUS_NAME);
2748 ti->name_sorting = NULL;
2749 ti->author = getStringCopy(parent->author);
2750 ti->year = getStringCopy(parent->year);
2752 ti->sort_priority = parent->sort_priority;
2753 ti->latest_engine = parent->latest_engine;
2754 ti->parent_link = FALSE;
2755 ti->in_user_dir = parent->in_user_dir;
2756 ti->user_defined = parent->user_defined;
2757 ti->color = parent->color;
2758 ti->class_desc = getStringCopy(parent->class_desc);
2760 ti->infotext = getStringCopy(parent->infotext);
2762 if (ti->type == TREE_TYPE_LEVEL_DIR)
2764 ti->imported_from = getStringCopy(parent->imported_from);
2765 ti->imported_by = getStringCopy(parent->imported_by);
2766 ti->tested_by = getStringCopy(parent->tested_by);
2768 ti->graphics_set_ecs = NULL;
2769 ti->graphics_set_aga = NULL;
2770 ti->graphics_set = NULL;
2771 ti->sounds_set = NULL;
2772 ti->music_set = NULL;
2773 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2774 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2775 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2777 ti->level_filename = NULL;
2778 ti->level_filetype = NULL;
2780 ti->special_flags = getStringCopy(parent->special_flags);
2783 ti->first_level = 0;
2785 ti->level_group = FALSE;
2786 ti->handicap_level = 0;
2788 ti->readonly = parent->readonly;
2790 ti->readonly = TRUE;
2792 ti->handicap = TRUE;
2793 ti->skip_levels = FALSE;
2797 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2799 TreeInfo *ti_copy = newTreeInfo();
2801 /* copy all values from the original structure */
2803 ti_copy->type = ti->type;
2805 ti_copy->node_top = ti->node_top;
2806 ti_copy->node_parent = ti->node_parent;
2807 ti_copy->node_group = ti->node_group;
2808 ti_copy->next = ti->next;
2810 ti_copy->cl_first = ti->cl_first;
2811 ti_copy->cl_cursor = ti->cl_cursor;
2813 ti_copy->subdir = getStringCopy(ti->subdir);
2814 ti_copy->fullpath = getStringCopy(ti->fullpath);
2815 ti_copy->basepath = getStringCopy(ti->basepath);
2816 ti_copy->identifier = getStringCopy(ti->identifier);
2817 ti_copy->name = getStringCopy(ti->name);
2818 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2819 ti_copy->author = getStringCopy(ti->author);
2820 ti_copy->year = getStringCopy(ti->year);
2821 ti_copy->imported_from = getStringCopy(ti->imported_from);
2822 ti_copy->imported_by = getStringCopy(ti->imported_by);
2823 ti_copy->tested_by = getStringCopy(ti->tested_by);
2825 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2826 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2827 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2828 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2829 ti_copy->music_set = getStringCopy(ti->music_set);
2830 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2831 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2832 ti_copy->music_path = getStringCopy(ti->music_path);
2834 ti_copy->level_filename = getStringCopy(ti->level_filename);
2835 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2837 ti_copy->special_flags = getStringCopy(ti->special_flags);
2839 ti_copy->levels = ti->levels;
2840 ti_copy->first_level = ti->first_level;
2841 ti_copy->last_level = ti->last_level;
2842 ti_copy->sort_priority = ti->sort_priority;
2844 ti_copy->latest_engine = ti->latest_engine;
2846 ti_copy->level_group = ti->level_group;
2847 ti_copy->parent_link = ti->parent_link;
2848 ti_copy->in_user_dir = ti->in_user_dir;
2849 ti_copy->user_defined = ti->user_defined;
2850 ti_copy->readonly = ti->readonly;
2851 ti_copy->handicap = ti->handicap;
2852 ti_copy->skip_levels = ti->skip_levels;
2854 ti_copy->color = ti->color;
2855 ti_copy->class_desc = getStringCopy(ti->class_desc);
2856 ti_copy->handicap_level = ti->handicap_level;
2858 ti_copy->infotext = getStringCopy(ti->infotext);
2863 void freeTreeInfo(TreeInfo *ti)
2868 checked_free(ti->subdir);
2869 checked_free(ti->fullpath);
2870 checked_free(ti->basepath);
2871 checked_free(ti->identifier);
2873 checked_free(ti->name);
2874 checked_free(ti->name_sorting);
2875 checked_free(ti->author);
2876 checked_free(ti->year);
2878 checked_free(ti->class_desc);
2880 checked_free(ti->infotext);
2882 if (ti->type == TREE_TYPE_LEVEL_DIR)
2884 checked_free(ti->imported_from);
2885 checked_free(ti->imported_by);
2886 checked_free(ti->tested_by);
2888 checked_free(ti->graphics_set_ecs);
2889 checked_free(ti->graphics_set_aga);
2890 checked_free(ti->graphics_set);
2891 checked_free(ti->sounds_set);
2892 checked_free(ti->music_set);
2894 checked_free(ti->graphics_path);
2895 checked_free(ti->sounds_path);
2896 checked_free(ti->music_path);
2898 checked_free(ti->level_filename);
2899 checked_free(ti->level_filetype);
2901 checked_free(ti->special_flags);
2904 // recursively free child node
2906 freeTreeInfo(ti->node_group);
2908 // recursively free next node
2910 freeTreeInfo(ti->next);
2915 void setSetupInfo(struct TokenInfo *token_info,
2916 int token_nr, char *token_value)
2918 int token_type = token_info[token_nr].type;
2919 void *setup_value = token_info[token_nr].value;
2921 if (token_value == NULL)
2924 /* set setup field to corresponding token value */
2929 *(boolean *)setup_value = get_boolean_from_string(token_value);
2933 *(int *)setup_value = get_switch3_from_string(token_value);
2937 *(Key *)setup_value = getKeyFromKeyName(token_value);
2941 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2945 *(int *)setup_value = get_integer_from_string(token_value);
2949 checked_free(*(char **)setup_value);
2950 *(char **)setup_value = getStringCopy(token_value);
2958 static int compareTreeInfoEntries(const void *object1, const void *object2)
2960 const TreeInfo *entry1 = *((TreeInfo **)object1);
2961 const TreeInfo *entry2 = *((TreeInfo **)object2);
2962 int class_sorting1 = 0, class_sorting2 = 0;
2965 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2967 class_sorting1 = LEVELSORTING(entry1);
2968 class_sorting2 = LEVELSORTING(entry2);
2970 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2971 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2972 entry1->type == TREE_TYPE_MUSIC_DIR)
2974 class_sorting1 = ARTWORKSORTING(entry1);
2975 class_sorting2 = ARTWORKSORTING(entry2);
2978 if (entry1->parent_link || entry2->parent_link)
2979 compare_result = (entry1->parent_link ? -1 : +1);
2980 else if (entry1->sort_priority == entry2->sort_priority)
2982 char *name1 = getStringToLower(entry1->name_sorting);
2983 char *name2 = getStringToLower(entry2->name_sorting);
2985 compare_result = strcmp(name1, name2);
2990 else if (class_sorting1 == class_sorting2)
2991 compare_result = entry1->sort_priority - entry2->sort_priority;
2993 compare_result = class_sorting1 - class_sorting2;
2995 return compare_result;
2998 static void createParentTreeInfoNode(TreeInfo *node_parent)
3002 if (node_parent == NULL)
3005 ti_new = newTreeInfo();
3006 setTreeInfoToDefaults(ti_new, node_parent->type);
3008 ti_new->node_parent = node_parent;
3009 ti_new->parent_link = TRUE;
3011 setString(&ti_new->identifier, node_parent->identifier);
3012 setString(&ti_new->name, ".. (parent directory)");
3013 setString(&ti_new->name_sorting, ti_new->name);
3015 setString(&ti_new->subdir, "..");
3016 setString(&ti_new->fullpath, node_parent->fullpath);
3018 ti_new->sort_priority = node_parent->sort_priority;
3019 ti_new->latest_engine = node_parent->latest_engine;
3021 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3023 pushTreeInfo(&node_parent->node_group, ti_new);
3027 /* -------------------------------------------------------------------------- */
3028 /* functions for handling level and custom artwork info cache */
3029 /* -------------------------------------------------------------------------- */
3031 static void LoadArtworkInfoCache()
3033 InitCacheDirectory();
3035 if (artworkinfo_cache_old == NULL)
3037 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3039 /* try to load artwork info hash from already existing cache file */
3040 artworkinfo_cache_old = loadSetupFileHash(filename);
3042 /* if no artwork info cache file was found, start with empty hash */
3043 if (artworkinfo_cache_old == NULL)
3044 artworkinfo_cache_old = newSetupFileHash();
3049 if (artworkinfo_cache_new == NULL)
3050 artworkinfo_cache_new = newSetupFileHash();
3053 static void SaveArtworkInfoCache()
3055 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3057 InitCacheDirectory();
3059 saveSetupFileHash(artworkinfo_cache_new, filename);
3064 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3066 static char *prefix = NULL;
3068 checked_free(prefix);
3070 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3075 /* (identical to above function, but separate string buffer needed -- nasty) */
3076 static char *getCacheToken(char *prefix, char *suffix)
3078 static char *token = NULL;
3080 checked_free(token);
3082 token = getStringCat2WithSeparator(prefix, suffix, ".");
3087 static char *getFileTimestampString(char *filename)
3090 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3092 struct stat file_status;
3094 if (stat(filename, &file_status) != 0) /* cannot stat file */
3095 return getStringCopy(i_to_a(0));
3097 return getStringCopy(i_to_a(file_status.st_mtime));
3101 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3103 struct stat file_status;
3105 if (timestamp_string == NULL)
3108 if (stat(filename, &file_status) != 0) /* cannot stat file */
3111 return (file_status.st_mtime != atoi(timestamp_string));
3114 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3116 char *identifier = level_node->subdir;
3117 char *type_string = ARTWORK_DIRECTORY(type);
3118 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3119 char *token_main = getCacheToken(token_prefix, "CACHED");
3120 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3121 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3122 TreeInfo *artwork_info = NULL;
3124 if (!use_artworkinfo_cache)
3131 artwork_info = newTreeInfo();
3132 setTreeInfoToDefaults(artwork_info, type);
3134 /* set all structure fields according to the token/value pairs */
3135 ldi = *artwork_info;
3136 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3138 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3139 char *value = getHashEntry(artworkinfo_cache_old, token);
3141 setSetupInfo(artworkinfo_tokens, i, value);
3143 /* check if cache entry for this item is invalid or incomplete */
3147 Error(ERR_WARN, "cache entry '%s' invalid", token);
3154 *artwork_info = ldi;
3159 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3160 LEVELINFO_FILENAME);
3161 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3162 ARTWORKINFO_FILENAME(type));
3164 /* check if corresponding "levelinfo.conf" file has changed */
3165 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3166 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3168 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3171 /* check if corresponding "<artworkinfo>.conf" file has changed */
3172 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3173 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3175 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3180 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
3183 checked_free(filename_levelinfo);
3184 checked_free(filename_artworkinfo);
3187 if (!cached && artwork_info != NULL)
3189 freeTreeInfo(artwork_info);
3194 return artwork_info;
3197 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3198 LevelDirTree *level_node, int type)
3200 char *identifier = level_node->subdir;
3201 char *type_string = ARTWORK_DIRECTORY(type);
3202 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3203 char *token_main = getCacheToken(token_prefix, "CACHED");
3204 boolean set_cache_timestamps = TRUE;
3207 setHashEntry(artworkinfo_cache_new, token_main, "true");
3209 if (set_cache_timestamps)
3211 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3212 LEVELINFO_FILENAME);
3213 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3214 ARTWORKINFO_FILENAME(type));
3215 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3216 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3218 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3219 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3221 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3222 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3224 checked_free(filename_levelinfo);
3225 checked_free(filename_artworkinfo);
3226 checked_free(timestamp_levelinfo);
3227 checked_free(timestamp_artworkinfo);
3230 ldi = *artwork_info;
3231 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3233 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3234 char *value = getSetupValue(artworkinfo_tokens[i].type,
3235 artworkinfo_tokens[i].value);
3237 setHashEntry(artworkinfo_cache_new, token, value);
3242 /* -------------------------------------------------------------------------- */
3243 /* functions for loading level info and custom artwork info */
3244 /* -------------------------------------------------------------------------- */
3246 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3247 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3249 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3250 TreeInfo *node_parent,
3251 char *level_directory,
3252 char *directory_name)
3255 static unsigned int progress_delay = 0;
3256 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3258 char *directory_path = getPath2(level_directory, directory_name);
3259 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3260 SetupFileHash *setup_file_hash;
3261 LevelDirTree *leveldir_new = NULL;
3264 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3265 if (!options.debug && !fileExists(filename))
3267 free(directory_path);
3273 setup_file_hash = loadSetupFileHash(filename);
3275 if (setup_file_hash == NULL)
3277 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3279 free(directory_path);
3285 leveldir_new = newTreeInfo();
3288 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3290 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3292 leveldir_new->subdir = getStringCopy(directory_name);
3294 checkSetupFileHashIdentifier(setup_file_hash, filename,
3295 getCookie("LEVELINFO"));
3297 /* set all structure fields according to the token/value pairs */
3298 ldi = *leveldir_new;
3299 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3300 setSetupInfo(levelinfo_tokens, i,
3301 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3302 *leveldir_new = ldi;
3304 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3305 setString(&leveldir_new->name, leveldir_new->subdir);
3307 if (leveldir_new->identifier == NULL)
3308 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3310 if (leveldir_new->name_sorting == NULL)
3311 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3313 if (node_parent == NULL) /* top level group */
3315 leveldir_new->basepath = getStringCopy(level_directory);
3316 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3318 else /* sub level group */
3320 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3321 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3325 if (leveldir_new->levels < 1)
3326 leveldir_new->levels = 1;
3329 leveldir_new->last_level =
3330 leveldir_new->first_level + leveldir_new->levels - 1;
3332 leveldir_new->in_user_dir =
3333 (!strEqual(leveldir_new->basepath, options.level_directory));
3336 printf("::: '%s' -> %d\n",
3337 leveldir_new->identifier,
3338 leveldir_new->in_user_dir);
3341 /* adjust some settings if user's private level directory was detected */
3342 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3343 leveldir_new->in_user_dir &&
3344 (strEqual(leveldir_new->subdir, getLoginName()) ||
3345 strEqual(leveldir_new->name, getLoginName()) ||
3346 strEqual(leveldir_new->author, getRealName())))
3348 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3349 leveldir_new->readonly = FALSE;
3352 leveldir_new->user_defined =
3353 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3355 leveldir_new->color = LEVELCOLOR(leveldir_new);
3357 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3359 leveldir_new->handicap_level = /* set handicap to default value */
3360 (leveldir_new->user_defined || !leveldir_new->handicap ?
3361 leveldir_new->last_level : leveldir_new->first_level);
3365 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3366 leveldir_new->level_group);
3368 if (leveldir_new->level_group ||
3369 DelayReached(&progress_delay, progress_delay_value))
3370 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3373 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3377 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3379 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3381 /* skip level sets without levels (which are probably artwork base sets) */
3383 freeSetupFileHash(setup_file_hash);
3384 free(directory_path);
3392 pushTreeInfo(node_first, leveldir_new);
3394 freeSetupFileHash(setup_file_hash);
3396 if (leveldir_new->level_group)
3398 /* create node to link back to current level directory */
3399 createParentTreeInfoNode(leveldir_new);
3401 /* recursively step into sub-directory and look for more level series */
3402 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3403 leveldir_new, directory_path);
3406 free(directory_path);
3413 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3414 TreeInfo *node_parent,
3415 char *level_directory)
3418 DirectoryEntry *dir_entry;
3419 boolean valid_entry_found = FALSE;
3422 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3425 if ((dir = openDirectory(level_directory)) == NULL)
3427 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3433 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3436 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3438 char *directory_name = dir_entry->basename;
3439 char *directory_path = getPath2(level_directory, directory_name);
3442 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3445 /* skip entries for current and parent directory */
3446 if (strEqual(directory_name, ".") ||
3447 strEqual(directory_name, ".."))
3449 free(directory_path);
3455 /* find out if directory entry is itself a directory */
3456 if (!dir_entry->is_directory) /* not a directory */
3458 free(directory_path);
3461 Error(ERR_INFO, "* entry '%s' is not a directory ...", directory_name);
3467 /* find out if directory entry is itself a directory */
3468 struct stat file_status;
3469 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3470 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3472 free(directory_path);
3478 free(directory_path);
3480 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3481 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3482 strEqual(directory_name, MUSIC_DIRECTORY))
3485 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3490 closeDirectory(dir);
3492 /* special case: top level directory may directly contain "levelinfo.conf" */
3493 if (node_parent == NULL && !valid_entry_found)
3495 /* check if this directory directly contains a file "levelinfo.conf" */
3496 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3497 level_directory, ".");
3500 if (!valid_entry_found)
3501 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3507 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3508 TreeInfo *node_parent,
3509 char *level_directory)
3512 struct dirent *dir_entry;
3513 boolean valid_entry_found = FALSE;
3516 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3519 if ((dir = opendir(level_directory)) == NULL)
3521 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3527 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3530 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3532 struct stat file_status;
3533 char *directory_name = dir_entry->d_name;
3534 char *directory_path = getPath2(level_directory, directory_name);
3537 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3540 /* skip entries for current and parent directory */
3541 if (strEqual(directory_name, ".") ||
3542 strEqual(directory_name, ".."))
3544 free(directory_path);
3548 /* find out if directory entry is itself a directory */
3549 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3550 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3552 free(directory_path);
3556 free(directory_path);
3558 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3559 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3560 strEqual(directory_name, MUSIC_DIRECTORY))
3563 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3570 /* special case: top level directory may directly contain "levelinfo.conf" */
3571 if (node_parent == NULL && !valid_entry_found)
3573 /* check if this directory directly contains a file "levelinfo.conf" */
3574 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3575 level_directory, ".");
3578 if (!valid_entry_found)
3579 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3584 boolean AdjustGraphicsForEMC()
3586 boolean settings_changed = FALSE;
3588 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3589 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3591 return settings_changed;
3594 void LoadLevelInfo()
3596 InitUserLevelDirectory(getLoginName());
3598 DrawInitText("Loading level series", 120, FC_GREEN);
3600 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3601 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3603 /* after loading all level set information, clone the level directory tree
3604 and remove all level sets without levels (these may still contain artwork
3605 to be offered in the setup menu as "custom artwork", and are therefore
3606 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3607 leveldir_first_all = leveldir_first;
3608 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3610 AdjustGraphicsForEMC();
3612 /* before sorting, the first entries will be from the user directory */
3613 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3615 if (leveldir_first == NULL)
3616 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3618 sortTreeInfo(&leveldir_first);
3621 dumpTreeInfo(leveldir_first, 0);
3627 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3628 TreeInfo *node_parent,
3629 char *base_directory,
3630 char *directory_name, int type)
3632 char *directory_path = getPath2(base_directory, directory_name);
3633 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3634 SetupFileHash *setup_file_hash = NULL;
3635 TreeInfo *artwork_new = NULL;
3638 if (fileExists(filename))
3639 setup_file_hash = loadSetupFileHash(filename);
3641 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3644 DirectoryEntry *dir_entry;
3645 boolean valid_file_found = FALSE;
3647 if ((dir = openDirectory(directory_path)) != NULL)
3649 while ((dir_entry = readDirectory(dir)) != NULL)
3651 char *entry_name = dir_entry->basename;
3653 if (FileIsArtworkType(entry_name, type))
3655 valid_file_found = TRUE;
3661 closeDirectory(dir);
3664 if (!valid_file_found)
3666 if (!strEqual(directory_name, "."))
3667 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3669 free(directory_path);
3676 artwork_new = newTreeInfo();
3679 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3681 setTreeInfoToDefaults(artwork_new, type);
3683 artwork_new->subdir = getStringCopy(directory_name);
3685 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3688 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3691 /* set all structure fields according to the token/value pairs */
3693 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3694 setSetupInfo(levelinfo_tokens, i,
3695 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3698 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3699 setString(&artwork_new->name, artwork_new->subdir);
3701 if (artwork_new->identifier == NULL)
3702 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3704 if (artwork_new->name_sorting == NULL)
3705 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3708 if (node_parent == NULL) /* top level group */
3710 artwork_new->basepath = getStringCopy(base_directory);
3711 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3713 else /* sub level group */
3715 artwork_new->basepath = getStringCopy(node_parent->basepath);
3716 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3719 artwork_new->in_user_dir =
3720 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3722 /* (may use ".sort_priority" from "setup_file_hash" above) */
3723 artwork_new->color = ARTWORKCOLOR(artwork_new);
3725 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3727 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3729 if (strEqual(artwork_new->subdir, "."))
3731 if (artwork_new->user_defined)
3733 setString(&artwork_new->identifier, "private");
3734 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3738 setString(&artwork_new->identifier, "classic");
3739 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3742 /* set to new values after changing ".sort_priority" */
3743 artwork_new->color = ARTWORKCOLOR(artwork_new);
3745 setString(&artwork_new->class_desc,
3746 getLevelClassDescription(artwork_new));
3750 setString(&artwork_new->identifier, artwork_new->subdir);
3753 setString(&artwork_new->name, artwork_new->identifier);
3754 setString(&artwork_new->name_sorting, artwork_new->name);
3758 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3761 pushTreeInfo(node_first, artwork_new);
3763 freeSetupFileHash(setup_file_hash);
3765 free(directory_path);
3773 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3774 TreeInfo *node_parent,
3775 char *base_directory,
3776 char *directory_name, int type)
3778 char *directory_path = getPath2(base_directory, directory_name);
3779 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3780 SetupFileHash *setup_file_hash = NULL;
3781 TreeInfo *artwork_new = NULL;
3784 if (fileExists(filename))
3785 setup_file_hash = loadSetupFileHash(filename);
3787 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3790 struct dirent *dir_entry;
3791 boolean valid_file_found = FALSE;
3793 if ((dir = opendir(directory_path)) != NULL)
3795 while ((dir_entry = readdir(dir)) != NULL)
3797 char *entry_name = dir_entry->d_name;
3799 if (FileIsArtworkType(entry_name, type))
3801 valid_file_found = TRUE;
3809 if (!valid_file_found)
3811 if (!strEqual(directory_name, "."))
3812 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3814 free(directory_path);
3821 artwork_new = newTreeInfo();
3824 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3826 setTreeInfoToDefaults(artwork_new, type);
3828 artwork_new->subdir = getStringCopy(directory_name);
3830 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3833 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3836 /* set all structure fields according to the token/value pairs */
3838 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3839 setSetupInfo(levelinfo_tokens, i,
3840 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3843 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3844 setString(&artwork_new->name, artwork_new->subdir);
3846 if (artwork_new->identifier == NULL)
3847 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3849 if (artwork_new->name_sorting == NULL)
3850 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3853 if (node_parent == NULL) /* top level group */
3855 artwork_new->basepath = getStringCopy(base_directory);
3856 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3858 else /* sub level group */
3860 artwork_new->basepath = getStringCopy(node_parent->basepath);
3861 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3864 artwork_new->in_user_dir =
3865 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3867 /* (may use ".sort_priority" from "setup_file_hash" above) */
3868 artwork_new->color = ARTWORKCOLOR(artwork_new);
3870 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3872 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3874 if (strEqual(artwork_new->subdir, "."))
3876 if (artwork_new->user_defined)
3878 setString(&artwork_new->identifier, "private");
3879 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3883 setString(&artwork_new->identifier, "classic");
3884 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3887 /* set to new values after changing ".sort_priority" */
3888 artwork_new->color = ARTWORKCOLOR(artwork_new);
3890 setString(&artwork_new->class_desc,
3891 getLevelClassDescription(artwork_new));
3895 setString(&artwork_new->identifier, artwork_new->subdir);
3898 setString(&artwork_new->name, artwork_new->identifier);
3899 setString(&artwork_new->name_sorting, artwork_new->name);
3903 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3906 pushTreeInfo(node_first, artwork_new);
3908 freeSetupFileHash(setup_file_hash);
3910 free(directory_path);
3920 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3921 TreeInfo *node_parent,
3922 char *base_directory, int type)
3925 DirectoryEntry *dir_entry;
3926 boolean valid_entry_found = FALSE;
3928 if ((dir = openDirectory(base_directory)) == NULL)
3930 /* display error if directory is main "options.graphics_directory" etc. */
3931 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3932 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3937 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3939 char *directory_name = dir_entry->basename;
3940 char *directory_path = getPath2(base_directory, directory_name);
3942 /* skip directory entries for current and parent directory */
3943 if (strEqual(directory_name, ".") ||
3944 strEqual(directory_name, ".."))
3946 free(directory_path);
3952 /* skip directory entries which are not a directory */
3953 if (!dir_entry->is_directory) /* not a directory */
3955 free(directory_path);
3960 /* skip directory entries which are not a directory or are not accessible */
3961 struct stat file_status;
3962 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3963 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3965 free(directory_path);
3971 free(directory_path);
3973 /* check if this directory contains artwork with or without config file */
3974 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3976 directory_name, type);
3979 closeDirectory(dir);
3981 /* check if this directory directly contains artwork itself */
3982 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3983 base_directory, ".",
3985 if (!valid_entry_found)
3986 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3992 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3993 TreeInfo *node_parent,
3994 char *base_directory, int type)
3997 struct dirent *dir_entry;
3998 boolean valid_entry_found = FALSE;
4000 if ((dir = opendir(base_directory)) == NULL)
4002 /* display error if directory is main "options.graphics_directory" etc. */
4003 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4004 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
4009 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
4011 struct stat file_status;
4012 char *directory_name = dir_entry->d_name;
4013 char *directory_path = getPath2(base_directory, directory_name);
4015 /* skip directory entries for current and parent directory */
4016 if (strEqual(directory_name, ".") ||
4017 strEqual(directory_name, ".."))
4019 free(directory_path);
4023 /* skip directory entries which are not a directory or are not accessible */
4024 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
4025 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
4027 free(directory_path);
4031 free(directory_path);
4033 /* check if this directory contains artwork with or without config file */
4034 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4036 directory_name, type);
4041 /* check if this directory directly contains artwork itself */
4042 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4043 base_directory, ".",
4045 if (!valid_entry_found)
4046 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4052 static TreeInfo *getDummyArtworkInfo(int type)
4054 /* this is only needed when there is completely no artwork available */
4055 TreeInfo *artwork_new = newTreeInfo();
4057 setTreeInfoToDefaults(artwork_new, type);
4059 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4060 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4061 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4063 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4064 setString(&artwork_new->name, UNDEFINED_FILENAME);
4065 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4070 void LoadArtworkInfo()
4072 LoadArtworkInfoCache();
4074 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
4076 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4077 options.graphics_directory,
4078 TREE_TYPE_GRAPHICS_DIR);
4079 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4080 getUserGraphicsDir(),
4081 TREE_TYPE_GRAPHICS_DIR);
4083 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4084 options.sounds_directory,
4085 TREE_TYPE_SOUNDS_DIR);
4086 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4088 TREE_TYPE_SOUNDS_DIR);
4090 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4091 options.music_directory,
4092 TREE_TYPE_MUSIC_DIR);
4093 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4095 TREE_TYPE_MUSIC_DIR);
4097 if (artwork.gfx_first == NULL)
4098 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4099 if (artwork.snd_first == NULL)
4100 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4101 if (artwork.mus_first == NULL)
4102 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4104 /* before sorting, the first entries will be from the user directory */
4105 artwork.gfx_current =
4106 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4107 if (artwork.gfx_current == NULL)
4108 artwork.gfx_current =
4109 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4110 if (artwork.gfx_current == NULL)
4111 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4113 artwork.snd_current =
4114 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4115 if (artwork.snd_current == NULL)
4116 artwork.snd_current =
4117 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4118 if (artwork.snd_current == NULL)
4119 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4121 artwork.mus_current =
4122 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4123 if (artwork.mus_current == NULL)
4124 artwork.mus_current =
4125 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4126 if (artwork.mus_current == NULL)
4127 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4129 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4130 artwork.snd_current_identifier = artwork.snd_current->identifier;
4131 artwork.mus_current_identifier = artwork.mus_current->identifier;
4134 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
4135 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
4136 printf("music set == %s\n\n", artwork.mus_current_identifier);
4139 sortTreeInfo(&artwork.gfx_first);
4140 sortTreeInfo(&artwork.snd_first);
4141 sortTreeInfo(&artwork.mus_first);
4144 dumpTreeInfo(artwork.gfx_first, 0);
4145 dumpTreeInfo(artwork.snd_first, 0);
4146 dumpTreeInfo(artwork.mus_first, 0);
4150 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
4151 LevelDirTree *level_node)
4154 static unsigned int progress_delay = 0;
4155 unsigned int progress_delay_value = 100; /* (in milliseconds) */
4157 int type = (*artwork_node)->type;
4159 /* recursively check all level directories for artwork sub-directories */
4163 /* check all tree entries for artwork, but skip parent link entries */
4164 if (!level_node->parent_link)
4166 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4167 boolean cached = (artwork_new != NULL);
4171 pushTreeInfo(artwork_node, artwork_new);
4175 TreeInfo *topnode_last = *artwork_node;
4176 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4177 ARTWORK_DIRECTORY(type));
4179 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4181 if (topnode_last != *artwork_node) /* check for newly added node */
4183 artwork_new = *artwork_node;
4185 setString(&artwork_new->identifier, level_node->subdir);
4186 setString(&artwork_new->name, level_node->name);
4187 setString(&artwork_new->name_sorting, level_node->name_sorting);
4189 artwork_new->sort_priority = level_node->sort_priority;
4190 artwork_new->color = LEVELCOLOR(artwork_new);
4196 /* insert artwork info (from old cache or filesystem) into new cache */
4197 if (artwork_new != NULL)
4198 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4202 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
4203 level_node->level_group);
4205 if (level_node->level_group ||
4206 DelayReached(&progress_delay, progress_delay_value))
4207 DrawInitText(level_node->name, 150, FC_YELLOW);
4210 if (level_node->node_group != NULL)
4211 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
4213 level_node = level_node->next;
4217 void LoadLevelArtworkInfo()
4219 print_timestamp_init("LoadLevelArtworkInfo");
4221 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4223 print_timestamp_time("DrawTimeText");
4225 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
4226 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4227 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
4228 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4229 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
4230 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4232 SaveArtworkInfoCache();
4234 print_timestamp_time("SaveArtworkInfoCache");
4236 /* needed for reloading level artwork not known at ealier stage */
4238 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
4240 artwork.gfx_current =
4241 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4242 if (artwork.gfx_current == NULL)
4243 artwork.gfx_current =
4244 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4245 if (artwork.gfx_current == NULL)
4246 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4249 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
4251 artwork.snd_current =
4252 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4253 if (artwork.snd_current == NULL)
4254 artwork.snd_current =
4255 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4256 if (artwork.snd_current == NULL)
4257 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4260 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
4262 artwork.mus_current =
4263 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4264 if (artwork.mus_current == NULL)
4265 artwork.mus_current =
4266 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4267 if (artwork.mus_current == NULL)
4268 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4271 print_timestamp_time("getTreeInfoFromIdentifier");
4273 sortTreeInfo(&artwork.gfx_first);
4274 sortTreeInfo(&artwork.snd_first);
4275 sortTreeInfo(&artwork.mus_first);
4277 print_timestamp_time("sortTreeInfo");
4280 dumpTreeInfo(artwork.gfx_first, 0);
4281 dumpTreeInfo(artwork.snd_first, 0);
4282 dumpTreeInfo(artwork.mus_first, 0);
4285 print_timestamp_done("LoadLevelArtworkInfo");
4288 static void SaveUserLevelInfo()
4290 LevelDirTree *level_info;
4295 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
4297 if (!(file = fopen(filename, MODE_WRITE)))
4299 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4304 level_info = newTreeInfo();
4306 /* always start with reliable default values */
4307 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4309 setString(&level_info->name, getLoginName());
4310 setString(&level_info->author, getRealName());
4311 level_info->levels = 100;
4312 level_info->first_level = 1;
4314 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4316 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4317 getCookie("LEVELINFO")));
4320 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4322 if (i == LEVELINFO_TOKEN_NAME ||
4323 i == LEVELINFO_TOKEN_AUTHOR ||
4324 i == LEVELINFO_TOKEN_LEVELS ||
4325 i == LEVELINFO_TOKEN_FIRST_LEVEL)
4326 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4328 /* just to make things nicer :) */
4329 if (i == LEVELINFO_TOKEN_AUTHOR)
4330 fprintf(file, "\n");
4333 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4337 SetFilePermissions(filename, PERMS_PRIVATE);
4339 freeTreeInfo(level_info);
4343 char *getSetupValue(int type, void *value)
4345 static char value_string[MAX_LINE_LEN];
4353 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4357 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4361 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4362 *(int *)value == FALSE ? "off" : "on"));
4366 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4369 case TYPE_YES_NO_AUTO:
4370 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4371 *(int *)value == FALSE ? "no" : "yes"));
4375 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4379 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4383 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4387 sprintf(value_string, "%d", *(int *)value);
4391 if (*(char **)value == NULL)
4394 strcpy(value_string, *(char **)value);
4398 value_string[0] = '\0';
4402 if (type & TYPE_GHOSTED)
4403 strcpy(value_string, "n/a");
4405 return value_string;
4408 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4412 static char token_string[MAX_LINE_LEN];
4413 int token_type = token_info[token_nr].type;
4414 void *setup_value = token_info[token_nr].value;
4415 char *token_text = token_info[token_nr].text;
4416 char *value_string = getSetupValue(token_type, setup_value);
4418 /* build complete token string */
4419 sprintf(token_string, "%s%s", prefix, token_text);
4421 /* build setup entry line */
4422 line = getFormattedSetupEntry(token_string, value_string);
4424 if (token_type == TYPE_KEY_X11)
4426 Key key = *(Key *)setup_value;
4427 char *keyname = getKeyNameFromKey(key);
4429 /* add comment, if useful */
4430 if (!strEqual(keyname, "(undefined)") &&
4431 !strEqual(keyname, "(unknown)"))
4433 /* add at least one whitespace */
4435 for (i = strlen(line); i < token_comment_position; i++)
4439 strcat(line, keyname);
4446 void LoadLevelSetup_LastSeries()
4448 /* ----------------------------------------------------------------------- */
4449 /* ~/.<program>/levelsetup.conf */
4450 /* ----------------------------------------------------------------------- */
4452 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4453 SetupFileHash *level_setup_hash = NULL;
4455 /* always start with reliable default values */
4456 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4458 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
4459 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4461 if (leveldir_current == NULL)
4462 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4465 if ((level_setup_hash = loadSetupFileHash(filename)))
4467 char *last_level_series =
4468 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4470 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4472 if (leveldir_current == NULL)
4473 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4475 checkSetupFileHashIdentifier(level_setup_hash, filename,
4476 getCookie("LEVELSETUP"));
4478 freeSetupFileHash(level_setup_hash);
4481 Error(ERR_WARN, "using default setup values");
4486 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4488 /* ----------------------------------------------------------------------- */
4489 /* ~/.<program>/levelsetup.conf */
4490 /* ----------------------------------------------------------------------- */
4492 // check if the current level directory structure is available at this point
4493 if (leveldir_current == NULL)
4496 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4497 char *level_subdir = leveldir_current->subdir;
4500 InitUserDataDirectory();
4502 if (!(file = fopen(filename, MODE_WRITE)))
4504 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4511 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4512 getCookie("LEVELSETUP")));
4514 if (deactivate_last_level_series)
4515 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4517 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4522 SetFilePermissions(filename, PERMS_PRIVATE);
4527 void SaveLevelSetup_LastSeries()
4529 SaveLevelSetup_LastSeries_Ext(FALSE);
4532 void SaveLevelSetup_LastSeries_Deactivate()
4534 SaveLevelSetup_LastSeries_Ext(TRUE);
4539 static void checkSeriesInfo()
4541 static char *level_directory = NULL;
4544 DirectoryEntry *dir_entry;
4547 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4549 level_directory = getPath2((leveldir_current->in_user_dir ?
4550 getUserLevelDir(NULL) :
4551 options.level_directory),
4552 leveldir_current->fullpath);
4554 if ((dir = openDirectory(level_directory)) == NULL)
4556 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4562 while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */
4564 if (strlen(dir_entry->basename) > 4 &&
4565 dir_entry->basename[3] == '.' &&
4566 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4568 char levelnum_str[4];
4571 strncpy(levelnum_str, dir_entry->basename, 3);
4572 levelnum_str[3] = '\0';
4574 levelnum_value = atoi(levelnum_str);
4576 if (levelnum_value < leveldir_current->first_level)
4578 Error(ERR_WARN, "additional level %d found", levelnum_value);
4579 leveldir_current->first_level = levelnum_value;
4581 else if (levelnum_value > leveldir_current->last_level)
4583 Error(ERR_WARN, "additional level %d found", levelnum_value);
4584 leveldir_current->last_level = levelnum_value;
4590 closeDirectory(dir);
4595 static void checkSeriesInfo()
4597 static char *level_directory = NULL;
4600 struct dirent *dir_entry;
4603 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4605 level_directory = getPath2((leveldir_current->in_user_dir ?
4606 getUserLevelDir(NULL) :
4607 options.level_directory),
4608 leveldir_current->fullpath);
4610 if ((dir = opendir(level_directory)) == NULL)
4612 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4618 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4620 if (strlen(dir_entry->d_name) > 4 &&
4621 dir_entry->d_name[3] == '.' &&
4622 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4624 char levelnum_str[4];
4627 strncpy(levelnum_str, dir_entry->d_name, 3);
4628 levelnum_str[3] = '\0';
4630 levelnum_value = atoi(levelnum_str);
4632 if (levelnum_value < leveldir_current->first_level)
4634 Error(ERR_WARN, "additional level %d found", levelnum_value);
4635 leveldir_current->first_level = levelnum_value;
4637 else if (levelnum_value > leveldir_current->last_level)
4639 Error(ERR_WARN, "additional level %d found", levelnum_value);
4640 leveldir_current->last_level = levelnum_value;
4651 void LoadLevelSetup_SeriesInfo()
4654 SetupFileHash *level_setup_hash = NULL;
4655 char *level_subdir = leveldir_current->subdir;
4658 /* always start with reliable default values */
4659 level_nr = leveldir_current->first_level;
4661 for (i = 0; i < MAX_LEVELS; i++)
4663 LevelStats_setPlayed(i, 0);
4664 LevelStats_setSolved(i, 0);
4667 checkSeriesInfo(leveldir_current);
4669 /* ----------------------------------------------------------------------- */
4670 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4671 /* ----------------------------------------------------------------------- */
4673 level_subdir = leveldir_current->subdir;
4675 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4677 if ((level_setup_hash = loadSetupFileHash(filename)))
4681 /* get last played level in this level set */
4683 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4687 level_nr = atoi(token_value);
4689 if (level_nr < leveldir_current->first_level)
4690 level_nr = leveldir_current->first_level;
4691 if (level_nr > leveldir_current->last_level)
4692 level_nr = leveldir_current->last_level;
4695 /* get handicap level in this level set */
4697 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4701 int level_nr = atoi(token_value);
4703 if (level_nr < leveldir_current->first_level)
4704 level_nr = leveldir_current->first_level;
4705 if (level_nr > leveldir_current->last_level + 1)
4706 level_nr = leveldir_current->last_level;
4708 if (leveldir_current->user_defined || !leveldir_current->handicap)
4709 level_nr = leveldir_current->last_level;
4711 leveldir_current->handicap_level = level_nr;
4714 /* get number of played and solved levels in this level set */
4716 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4718 char *token = HASH_ITERATION_TOKEN(itr);
4719 char *value = HASH_ITERATION_VALUE(itr);
4721 if (strlen(token) == 3 &&
4722 token[0] >= '0' && token[0] <= '9' &&
4723 token[1] >= '0' && token[1] <= '9' &&
4724 token[2] >= '0' && token[2] <= '9')
4726 int level_nr = atoi(token);
4729 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4731 value = strchr(value, ' ');
4734 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4737 END_HASH_ITERATION(hash, itr)
4739 checkSetupFileHashIdentifier(level_setup_hash, filename,
4740 getCookie("LEVELSETUP"));
4742 freeSetupFileHash(level_setup_hash);
4745 Error(ERR_WARN, "using default setup values");
4750 void SaveLevelSetup_SeriesInfo()
4753 char *level_subdir = leveldir_current->subdir;
4754 char *level_nr_str = int2str(level_nr, 0);
4755 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4759 /* ----------------------------------------------------------------------- */
4760 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4761 /* ----------------------------------------------------------------------- */
4763 InitLevelSetupDirectory(level_subdir);
4765 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4767 if (!(file = fopen(filename, MODE_WRITE)))
4769 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4774 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4775 getCookie("LEVELSETUP")));
4776 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4778 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4779 handicap_level_str));
4781 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4784 if (LevelStats_getPlayed(i) > 0 ||
4785 LevelStats_getSolved(i) > 0)
4790 sprintf(token, "%03d", i);
4791 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4793 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4799 SetFilePermissions(filename, PERMS_PRIVATE);
4804 int LevelStats_getPlayed(int nr)
4806 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4809 int LevelStats_getSolved(int nr)
4811 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4814 void LevelStats_setPlayed(int nr, int value)
4816 if (nr >= 0 && nr < MAX_LEVELS)
4817 level_stats[nr].played = value;
4820 void LevelStats_setSolved(int nr, int value)
4822 if (nr >= 0 && nr < MAX_LEVELS)
4823 level_stats[nr].solved = value;
4826 void LevelStats_incPlayed(int nr)
4828 if (nr >= 0 && nr < MAX_LEVELS)
4829 level_stats[nr].played++;
4832 void LevelStats_incSolved(int nr)
4834 if (nr >= 0 && nr < MAX_LEVELS)
4835 level_stats[nr].solved++;