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 #if defined(PLATFORM_MSDOS)
604 if (program.filename_prefix != NULL)
606 int prefix_len = strlen(program.filename_prefix);
608 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
609 basename_corrected = &basename[prefix_len];
611 /* if corrected filename is still longer than standard MS-DOS filename
612 size (8 characters + 1 dot + 3 characters file extension), shorten
613 filename by writing file extension after 8th basename character */
614 if (strlen(basename_corrected) > 8 + 1 + 3)
616 static char *msdos_filename = NULL;
618 checked_free(msdos_filename);
620 msdos_filename = getStringCopy(basename_corrected);
621 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
623 basename_corrected = msdos_filename;
628 return basename_corrected;
631 char *getCustomImageFilename(char *basename)
633 static char *filename = NULL;
634 boolean skip_setup_artwork = FALSE;
636 checked_free(filename);
638 basename = getCorrectedArtworkBasename(basename);
640 if (!gfx.override_level_graphics)
642 /* 1st try: look for special artwork in current level series directory */
643 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
644 if (fileExists(filename))
649 /* check if there is special artwork configured in level series config */
650 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
652 /* 2nd try: look for special artwork configured in level series config */
653 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
654 if (fileExists(filename))
659 /* take missing artwork configured in level set config from default */
660 skip_setup_artwork = TRUE;
664 if (!skip_setup_artwork)
666 /* 3rd try: look for special artwork in configured artwork directory */
667 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
668 if (fileExists(filename))
674 /* 4th try: look for default artwork in new default artwork directory */
675 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
676 if (fileExists(filename))
681 /* 5th try: look for default artwork in old default artwork directory */
682 filename = getPath2(options.graphics_directory, basename);
683 if (fileExists(filename))
686 #if defined(CREATE_SPECIAL_EDITION)
690 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
692 /* 6th try: look for fallback artwork in old default artwork directory */
693 /* (needed to prevent errors when trying to access unused artwork files) */
694 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
695 if (fileExists(filename))
699 return NULL; /* cannot find specified artwork file anywhere */
702 char *getCustomSoundFilename(char *basename)
704 static char *filename = NULL;
705 boolean skip_setup_artwork = FALSE;
707 checked_free(filename);
709 basename = getCorrectedArtworkBasename(basename);
711 if (!gfx.override_level_sounds)
713 /* 1st try: look for special artwork in current level series directory */
714 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
715 if (fileExists(filename))
720 /* check if there is special artwork configured in level series config */
721 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
723 /* 2nd try: look for special artwork configured in level series config */
724 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
725 if (fileExists(filename))
730 /* take missing artwork configured in level set config from default */
731 skip_setup_artwork = TRUE;
735 if (!skip_setup_artwork)
737 /* 3rd try: look for special artwork in configured artwork directory */
738 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
739 if (fileExists(filename))
745 /* 4th try: look for default artwork in new default artwork directory */
746 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
747 if (fileExists(filename))
752 /* 5th try: look for default artwork in old default artwork directory */
753 filename = getPath2(options.sounds_directory, basename);
754 if (fileExists(filename))
757 #if defined(CREATE_SPECIAL_EDITION)
761 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
763 /* 6th try: look for fallback artwork in old default artwork directory */
764 /* (needed to prevent errors when trying to access unused artwork files) */
765 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
766 if (fileExists(filename))
770 return NULL; /* cannot find specified artwork file anywhere */
773 char *getCustomMusicFilename(char *basename)
775 static char *filename = NULL;
776 boolean skip_setup_artwork = FALSE;
778 checked_free(filename);
780 basename = getCorrectedArtworkBasename(basename);
782 if (!gfx.override_level_music)
784 /* 1st try: look for special artwork in current level series directory */
785 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
786 if (fileExists(filename))
791 /* check if there is special artwork configured in level series config */
792 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
794 /* 2nd try: look for special artwork configured in level series config */
795 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
796 if (fileExists(filename))
801 /* take missing artwork configured in level set config from default */
802 skip_setup_artwork = TRUE;
806 if (!skip_setup_artwork)
808 /* 3rd try: look for special artwork in configured artwork directory */
809 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
810 if (fileExists(filename))
816 /* 4th try: look for default artwork in new default artwork directory */
817 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
818 if (fileExists(filename))
823 /* 5th try: look for default artwork in old default artwork directory */
824 filename = getPath2(options.music_directory, basename);
825 if (fileExists(filename))
828 #if defined(CREATE_SPECIAL_EDITION)
832 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
834 /* 6th try: look for fallback artwork in old default artwork directory */
835 /* (needed to prevent errors when trying to access unused artwork files) */
836 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
837 if (fileExists(filename))
841 return NULL; /* cannot find specified artwork file anywhere */
844 char *getCustomArtworkFilename(char *basename, int type)
846 if (type == ARTWORK_TYPE_GRAPHICS)
847 return getCustomImageFilename(basename);
848 else if (type == ARTWORK_TYPE_SOUNDS)
849 return getCustomSoundFilename(basename);
850 else if (type == ARTWORK_TYPE_MUSIC)
851 return getCustomMusicFilename(basename);
853 return UNDEFINED_FILENAME;
856 char *getCustomArtworkConfigFilename(int type)
858 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
861 char *getCustomArtworkLevelConfigFilename(int type)
863 static char *filename = NULL;
865 checked_free(filename);
867 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
872 char *getCustomMusicDirectory(void)
874 static char *directory = NULL;
875 boolean skip_setup_artwork = FALSE;
877 checked_free(directory);
879 if (!gfx.override_level_music)
881 /* 1st try: look for special artwork in current level series directory */
882 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
883 if (directoryExists(directory))
888 /* check if there is special artwork configured in level series config */
889 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
891 /* 2nd try: look for special artwork configured in level series config */
892 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
893 if (directoryExists(directory))
898 /* take missing artwork configured in level set config from default */
899 skip_setup_artwork = TRUE;
903 if (!skip_setup_artwork)
905 /* 3rd try: look for special artwork in configured artwork directory */
906 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
907 if (directoryExists(directory))
913 /* 4th try: look for default artwork in new default artwork directory */
914 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
915 if (directoryExists(directory))
920 /* 5th try: look for default artwork in old default artwork directory */
921 directory = getStringCopy(options.music_directory);
922 if (directoryExists(directory))
925 return NULL; /* cannot find specified artwork file anywhere */
928 void InitTapeDirectory(char *level_subdir)
930 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
931 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
932 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
935 void InitScoreDirectory(char *level_subdir)
937 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
938 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
939 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
942 static void SaveUserLevelInfo();
944 void InitUserLevelDirectory(char *level_subdir)
946 if (!directoryExists(getUserLevelDir(level_subdir)))
948 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
949 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
950 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
956 void InitLevelSetupDirectory(char *level_subdir)
958 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
959 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
960 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
963 void InitCacheDirectory()
965 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
966 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
970 /* ------------------------------------------------------------------------- */
971 /* some functions to handle lists of level and artwork directories */
972 /* ------------------------------------------------------------------------- */
974 TreeInfo *newTreeInfo()
976 return checked_calloc(sizeof(TreeInfo));
979 TreeInfo *newTreeInfo_setDefaults(int type)
981 TreeInfo *ti = newTreeInfo();
983 setTreeInfoToDefaults(ti, type);
988 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
990 node_new->next = *node_first;
991 *node_first = node_new;
994 int numTreeInfo(TreeInfo *node)
1007 boolean validLevelSeries(TreeInfo *node)
1009 return (node != NULL && !node->node_group && !node->parent_link);
1012 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1017 if (node->node_group) /* enter level group (step down into tree) */
1018 return getFirstValidTreeInfoEntry(node->node_group);
1019 else if (node->parent_link) /* skip start entry of level group */
1021 if (node->next) /* get first real level series entry */
1022 return getFirstValidTreeInfoEntry(node->next);
1023 else /* leave empty level group and go on */
1024 return getFirstValidTreeInfoEntry(node->node_parent->next);
1026 else /* this seems to be a regular level series */
1030 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1035 if (node->node_parent == NULL) /* top level group */
1036 return *node->node_top;
1037 else /* sub level group */
1038 return node->node_parent->node_group;
1041 int numTreeInfoInGroup(TreeInfo *node)
1043 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1046 int posTreeInfo(TreeInfo *node)
1048 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1053 if (node_cmp == node)
1057 node_cmp = node_cmp->next;
1063 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1065 TreeInfo *node_default = node;
1077 return node_default;
1080 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1082 if (identifier == NULL)
1087 if (node->node_group)
1089 TreeInfo *node_group;
1091 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1096 else if (!node->parent_link)
1098 if (strEqual(identifier, node->identifier))
1108 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1109 TreeInfo *node, boolean skip_sets_without_levels)
1116 if (!node->parent_link && !node->level_group &&
1117 skip_sets_without_levels && node->levels == 0)
1118 return cloneTreeNode(node_top, node_parent, node->next,
1119 skip_sets_without_levels);
1122 node_new = getTreeInfoCopy(node); /* copy complete node */
1124 node_new = newTreeInfo();
1126 *node_new = *node; /* copy complete node */
1129 node_new->node_top = node_top; /* correct top node link */
1130 node_new->node_parent = node_parent; /* correct parent node link */
1132 if (node->level_group)
1133 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1134 skip_sets_without_levels);
1136 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1137 skip_sets_without_levels);
1142 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1144 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1146 *ti_new = ti_cloned;
1149 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1151 boolean settings_changed = FALSE;
1155 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1156 !strEqual(node->graphics_set, node->graphics_set_ecs))
1158 setString(&node->graphics_set, node->graphics_set_ecs);
1159 settings_changed = TRUE;
1161 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1162 !strEqual(node->graphics_set, node->graphics_set_aga))
1164 setString(&node->graphics_set, node->graphics_set_aga);
1165 settings_changed = TRUE;
1168 if (node->node_group != NULL)
1169 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1174 return settings_changed;
1177 void dumpTreeInfo(TreeInfo *node, int depth)
1181 printf("Dumping TreeInfo:\n");
1185 for (i = 0; i < (depth + 1) * 3; i++)
1188 printf("'%s' / '%s'\n", node->identifier, node->name);
1191 // use for dumping artwork info tree
1192 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1193 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1196 if (node->node_group != NULL)
1197 dumpTreeInfo(node->node_group, depth + 1);
1203 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1204 int (*compare_function)(const void *,
1207 int num_nodes = numTreeInfo(*node_first);
1208 TreeInfo **sort_array;
1209 TreeInfo *node = *node_first;
1215 /* allocate array for sorting structure pointers */
1216 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1218 /* writing structure pointers to sorting array */
1219 while (i < num_nodes && node) /* double boundary check... */
1221 sort_array[i] = node;
1227 /* sorting the structure pointers in the sorting array */
1228 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1231 /* update the linkage of list elements with the sorted node array */
1232 for (i = 0; i < num_nodes - 1; i++)
1233 sort_array[i]->next = sort_array[i + 1];
1234 sort_array[num_nodes - 1]->next = NULL;
1236 /* update the linkage of the main list anchor pointer */
1237 *node_first = sort_array[0];
1241 /* now recursively sort the level group structures */
1245 if (node->node_group != NULL)
1246 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1252 void sortTreeInfo(TreeInfo **node_first)
1254 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1258 /* ========================================================================= */
1259 /* some stuff from "files.c" */
1260 /* ========================================================================= */
1262 #if defined(PLATFORM_WIN32)
1264 #define S_IRGRP S_IRUSR
1267 #define S_IROTH S_IRUSR
1270 #define S_IWGRP S_IWUSR
1273 #define S_IWOTH S_IWUSR
1276 #define S_IXGRP S_IXUSR
1279 #define S_IXOTH S_IXUSR
1282 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1287 #endif /* PLATFORM_WIN32 */
1289 /* file permissions for newly written files */
1290 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1291 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1292 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1294 #define MODE_W_PRIVATE (S_IWUSR)
1295 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1296 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1298 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1299 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1301 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1302 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1306 static char *dir = NULL;
1308 #if defined(PLATFORM_WIN32)
1311 dir = checked_malloc(MAX_PATH + 1);
1313 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1316 #elif defined(PLATFORM_UNIX)
1319 if ((dir = getenv("HOME")) == NULL)
1323 if ((pwd = getpwuid(getuid())) != NULL)
1324 dir = getStringCopy(pwd->pw_dir);
1336 char *getCommonDataDir(void)
1338 static char *common_data_dir = NULL;
1340 #if defined(PLATFORM_WIN32)
1341 if (common_data_dir == NULL)
1343 char *dir = checked_malloc(MAX_PATH + 1);
1345 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1346 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1347 common_data_dir = getPath2(dir, program.userdata_subdir);
1349 common_data_dir = options.rw_base_directory;
1352 if (common_data_dir == NULL)
1353 common_data_dir = options.rw_base_directory;
1356 return common_data_dir;
1359 char *getPersonalDataDir(void)
1361 static char *personal_data_dir = NULL;
1363 #if defined(PLATFORM_MACOSX)
1364 if (personal_data_dir == NULL)
1365 personal_data_dir = getPath2(getHomeDir(), "Documents");
1367 if (personal_data_dir == NULL)
1368 personal_data_dir = getHomeDir();
1371 return personal_data_dir;
1374 char *getUserGameDataDir(void)
1376 static char *user_game_data_dir = NULL;
1378 #if defined(PLATFORM_ANDROID)
1379 if (user_game_data_dir == NULL)
1380 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1382 if (user_game_data_dir == NULL)
1383 user_game_data_dir = getPath2(getPersonalDataDir(),
1384 program.userdata_subdir);
1387 return user_game_data_dir;
1390 void updateUserGameDataDir()
1392 #if defined(PLATFORM_MACOSX)
1393 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1394 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1396 /* convert old Unix style game data directory to Mac OS X style, if needed */
1397 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1399 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1401 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1402 userdata_dir_old, userdata_dir_new);
1404 /* continue using Unix style data directory -- this should not happen */
1405 program.userdata_path = getPath2(getPersonalDataDir(),
1406 program.userdata_subdir_unix);
1410 free(userdata_dir_old);
1416 return getUserGameDataDir();
1419 static mode_t posix_umask(mode_t mask)
1421 #if defined(PLATFORM_UNIX)
1428 static int posix_mkdir(const char *pathname, mode_t mode)
1430 #if defined(PLATFORM_WIN32)
1431 return mkdir(pathname);
1433 return mkdir(pathname, mode);
1437 static boolean posix_process_running_setgid()
1439 #if defined(PLATFORM_UNIX)
1440 return (getgid() != getegid());
1446 void createDirectory(char *dir, char *text, int permission_class)
1448 /* leave "other" permissions in umask untouched, but ensure group parts
1449 of USERDATA_DIR_MODE are not masked */
1450 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1451 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1452 mode_t last_umask = posix_umask(0);
1453 mode_t group_umask = ~(dir_mode & S_IRWXG);
1454 int running_setgid = posix_process_running_setgid();
1456 /* if we're setgid, protect files against "other" */
1457 /* else keep umask(0) to make the dir world-writable */
1460 posix_umask(last_umask & group_umask);
1462 dir_mode |= MODE_W_ALL;
1464 if (!directoryExists(dir))
1465 if (posix_mkdir(dir, dir_mode) != 0)
1466 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1467 text, dir, strerror(errno));
1469 if (permission_class == PERMS_PUBLIC && !running_setgid)
1470 chmod(dir, dir_mode);
1472 posix_umask(last_umask); /* restore previous umask */
1475 void InitUserDataDirectory()
1477 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1480 void SetFilePermissions(char *filename, int permission_class)
1482 int running_setgid = posix_process_running_setgid();
1483 int perms = (permission_class == PERMS_PRIVATE ?
1484 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1486 if (permission_class == PERMS_PUBLIC && !running_setgid)
1487 perms |= MODE_W_ALL;
1489 chmod(filename, perms);
1492 char *getCookie(char *file_type)
1494 static char cookie[MAX_COOKIE_LEN + 1];
1496 if (strlen(program.cookie_prefix) + 1 +
1497 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1498 return "[COOKIE ERROR]"; /* should never happen */
1500 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1501 program.cookie_prefix, file_type,
1502 program.version_major, program.version_minor);
1507 int getFileVersionFromCookieString(const char *cookie)
1509 const char *ptr_cookie1, *ptr_cookie2;
1510 const char *pattern1 = "_FILE_VERSION_";
1511 const char *pattern2 = "?.?";
1512 const int len_cookie = strlen(cookie);
1513 const int len_pattern1 = strlen(pattern1);
1514 const int len_pattern2 = strlen(pattern2);
1515 const int len_pattern = len_pattern1 + len_pattern2;
1516 int version_major, version_minor;
1518 if (len_cookie <= len_pattern)
1521 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1522 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1524 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1527 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1528 ptr_cookie2[1] != '.' ||
1529 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1532 version_major = ptr_cookie2[0] - '0';
1533 version_minor = ptr_cookie2[2] - '0';
1535 return VERSION_IDENT(version_major, version_minor, 0, 0);
1538 boolean checkCookieString(const char *cookie, const char *template)
1540 const char *pattern = "_FILE_VERSION_?.?";
1541 const int len_cookie = strlen(cookie);
1542 const int len_template = strlen(template);
1543 const int len_pattern = strlen(pattern);
1545 if (len_cookie != len_template)
1548 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1554 /* ------------------------------------------------------------------------- */
1555 /* setup file list and hash handling functions */
1556 /* ------------------------------------------------------------------------- */
1558 char *getFormattedSetupEntry(char *token, char *value)
1561 static char entry[MAX_LINE_LEN];
1563 /* if value is an empty string, just return token without value */
1567 /* start with the token and some spaces to format output line */
1568 sprintf(entry, "%s:", token);
1569 for (i = strlen(entry); i < token_value_position; i++)
1572 /* continue with the token's value */
1573 strcat(entry, value);
1578 SetupFileList *newSetupFileList(char *token, char *value)
1580 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1582 new->token = getStringCopy(token);
1583 new->value = getStringCopy(value);
1590 void freeSetupFileList(SetupFileList *list)
1595 checked_free(list->token);
1596 checked_free(list->value);
1599 freeSetupFileList(list->next);
1604 char *getListEntry(SetupFileList *list, char *token)
1609 if (strEqual(list->token, token))
1612 return getListEntry(list->next, token);
1615 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1620 if (strEqual(list->token, token))
1622 checked_free(list->value);
1624 list->value = getStringCopy(value);
1628 else if (list->next == NULL)
1629 return (list->next = newSetupFileList(token, value));
1631 return setListEntry(list->next, token, value);
1634 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1639 if (list->next == NULL)
1640 return (list->next = newSetupFileList(token, value));
1642 return addListEntry(list->next, token, value);
1647 static void printSetupFileList(SetupFileList *list)
1652 printf("token: '%s'\n", list->token);
1653 printf("value: '%s'\n", list->value);
1655 printSetupFileList(list->next);
1661 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1662 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1663 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1664 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1666 #define insert_hash_entry hashtable_insert
1667 #define search_hash_entry hashtable_search
1668 #define change_hash_entry hashtable_change
1669 #define remove_hash_entry hashtable_remove
1672 unsigned int get_hash_from_key(void *key)
1677 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1678 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1679 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1680 it works better than many other constants, prime or not) has never been
1681 adequately explained.
1683 If you just want to have a good hash function, and cannot wait, djb2
1684 is one of the best string hash functions i know. It has excellent
1685 distribution and speed on many different sets of keys and table sizes.
1686 You are not likely to do better with one of the "well known" functions
1687 such as PJW, K&R, etc.
1689 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1692 char *str = (char *)key;
1693 unsigned int hash = 5381;
1696 while ((c = *str++))
1697 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1702 static int keys_are_equal(void *key1, void *key2)
1704 return (strEqual((char *)key1, (char *)key2));
1707 SetupFileHash *newSetupFileHash()
1709 SetupFileHash *new_hash =
1710 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1712 if (new_hash == NULL)
1713 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1718 void freeSetupFileHash(SetupFileHash *hash)
1723 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1726 char *getHashEntry(SetupFileHash *hash, char *token)
1731 return search_hash_entry(hash, token);
1734 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1741 value_copy = getStringCopy(value);
1743 /* change value; if it does not exist, insert it as new */
1744 if (!change_hash_entry(hash, token, value_copy))
1745 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1746 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1749 char *removeHashEntry(SetupFileHash *hash, char *token)
1754 return remove_hash_entry(hash, token);
1758 static void printSetupFileHash(SetupFileHash *hash)
1760 BEGIN_HASH_ITERATION(hash, itr)
1762 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1763 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1765 END_HASH_ITERATION(hash, itr)
1769 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1770 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1771 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1773 static boolean token_value_separator_found = FALSE;
1774 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1775 static boolean token_value_separator_warning = FALSE;
1777 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1778 static boolean token_already_exists_warning = FALSE;
1781 static boolean getTokenValueFromSetupLineExt(char *line,
1782 char **token_ptr, char **value_ptr,
1783 char *filename, char *line_raw,
1785 boolean separator_required)
1787 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1788 char *token, *value, *line_ptr;
1790 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1791 if (line_raw == NULL)
1793 strncpy(line_copy, line, MAX_LINE_LEN);
1794 line_copy[MAX_LINE_LEN] = '\0';
1797 strcpy(line_raw_copy, line_copy);
1798 line_raw = line_raw_copy;
1801 /* cut trailing comment from input line */
1802 for (line_ptr = line; *line_ptr; line_ptr++)
1804 if (*line_ptr == '#')
1811 /* cut trailing whitespaces from input line */
1812 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1813 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1816 /* ignore empty lines */
1820 /* cut leading whitespaces from token */
1821 for (token = line; *token; token++)
1822 if (*token != ' ' && *token != '\t')
1825 /* start with empty value as reliable default */
1828 token_value_separator_found = FALSE;
1830 /* find end of token to determine start of value */
1831 for (line_ptr = token; *line_ptr; line_ptr++)
1834 /* first look for an explicit token/value separator, like ':' or '=' */
1835 if (*line_ptr == ':' || *line_ptr == '=')
1837 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1840 *line_ptr = '\0'; /* terminate token string */
1841 value = line_ptr + 1; /* set beginning of value */
1843 token_value_separator_found = TRUE;
1849 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1850 /* fallback: if no token/value separator found, also allow whitespaces */
1851 if (!token_value_separator_found && !separator_required)
1853 for (line_ptr = token; *line_ptr; line_ptr++)
1855 if (*line_ptr == ' ' || *line_ptr == '\t')
1857 *line_ptr = '\0'; /* terminate token string */
1858 value = line_ptr + 1; /* set beginning of value */
1860 token_value_separator_found = TRUE;
1866 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1867 if (token_value_separator_found)
1869 if (!token_value_separator_warning)
1871 Error(ERR_INFO_LINE, "-");
1873 if (filename != NULL)
1875 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1876 Error(ERR_INFO, "- config file: '%s'", filename);
1880 Error(ERR_WARN, "missing token/value separator(s):");
1883 token_value_separator_warning = TRUE;
1886 if (filename != NULL)
1887 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1889 Error(ERR_INFO, "- line: '%s'", line_raw);
1895 /* cut trailing whitespaces from token */
1896 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1897 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1900 /* cut leading whitespaces from value */
1901 for (; *value; value++)
1902 if (*value != ' ' && *value != '\t')
1907 value = "true"; /* treat tokens without value as "true" */
1916 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1918 /* while the internal (old) interface does not require a token/value
1919 separator (for downwards compatibility with existing files which
1920 don't use them), it is mandatory for the external (new) interface */
1922 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1928 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1929 boolean top_recursion_level, boolean is_hash)
1931 static SetupFileHash *include_filename_hash = NULL;
1932 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1933 char *token, *value, *line_ptr;
1934 void *insert_ptr = NULL;
1935 boolean read_continued_line = FALSE;
1937 int line_nr = 0, token_count = 0, include_count = 0;
1939 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1940 token_value_separator_warning = FALSE;
1943 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1944 token_already_exists_warning = FALSE;
1948 Error(ERR_INFO, "===== opening file: '%s'", filename);
1951 if (!(file = openFile(filename, MODE_READ)))
1953 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1959 Error(ERR_INFO, "===== reading file: '%s'", filename);
1962 /* use "insert pointer" to store list end for constant insertion complexity */
1964 insert_ptr = setup_file_data;
1966 /* on top invocation, create hash to mark included files (to prevent loops) */
1967 if (top_recursion_level)
1968 include_filename_hash = newSetupFileHash();
1970 /* mark this file as already included (to prevent including it again) */
1971 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1973 while (!checkEndOfFile(file))
1975 /* read next line of input file */
1976 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1980 Error(ERR_INFO, "got line: '%s'", line);
1983 /* check if line was completely read and is terminated by line break */
1984 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1987 /* cut trailing line break (this can be newline and/or carriage return) */
1988 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1989 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1992 /* copy raw input line for later use (mainly debugging output) */
1993 strcpy(line_raw, line);
1995 if (read_continued_line)
1998 /* !!! ??? WHY ??? !!! */
1999 /* cut leading whitespaces from input line */
2000 for (line_ptr = line; *line_ptr; line_ptr++)
2001 if (*line_ptr != ' ' && *line_ptr != '\t')
2005 /* append new line to existing line, if there is enough space */
2006 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2007 strcat(previous_line, line_ptr);
2009 strcpy(line, previous_line); /* copy storage buffer to line */
2011 read_continued_line = FALSE;
2014 /* if the last character is '\', continue at next line */
2015 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2017 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2018 strcpy(previous_line, line); /* copy line to storage buffer */
2020 read_continued_line = TRUE;
2025 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2026 line_raw, line_nr, FALSE))
2031 if (strEqual(token, "include"))
2033 if (getHashEntry(include_filename_hash, value) == NULL)
2035 char *basepath = getBasePath(filename);
2036 char *basename = getBaseName(value);
2037 char *filename_include = getPath2(basepath, basename);
2040 Error(ERR_INFO, "[including file '%s']", filename_include);
2043 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2047 free(filename_include);
2053 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2060 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2062 getHashEntry((SetupFileHash *)setup_file_data, token);
2064 if (old_value != NULL)
2066 if (!token_already_exists_warning)
2068 Error(ERR_INFO_LINE, "-");
2069 Error(ERR_WARN, "duplicate token(s) found in config file:");
2070 Error(ERR_INFO, "- config file: '%s'", filename);
2072 token_already_exists_warning = TRUE;
2075 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2076 Error(ERR_INFO, " old value: '%s'", old_value);
2077 Error(ERR_INFO, " new value: '%s'", value);
2081 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2085 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2095 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2096 if (token_value_separator_warning)
2097 Error(ERR_INFO_LINE, "-");
2100 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2101 if (token_already_exists_warning)
2102 Error(ERR_INFO_LINE, "-");
2105 if (token_count == 0 && include_count == 0)
2106 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2108 if (top_recursion_level)
2109 freeSetupFileHash(include_filename_hash);
2116 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2117 boolean top_recursion_level, boolean is_hash)
2119 static SetupFileHash *include_filename_hash = NULL;
2120 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2121 char *token, *value, *line_ptr;
2122 void *insert_ptr = NULL;
2123 boolean read_continued_line = FALSE;
2125 int line_nr = 0, token_count = 0, include_count = 0;
2127 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2128 token_value_separator_warning = FALSE;
2131 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2132 token_already_exists_warning = FALSE;
2135 if (!(file = fopen(filename, MODE_READ)))
2137 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2142 /* use "insert pointer" to store list end for constant insertion complexity */
2144 insert_ptr = setup_file_data;
2146 /* on top invocation, create hash to mark included files (to prevent loops) */
2147 if (top_recursion_level)
2148 include_filename_hash = newSetupFileHash();
2150 /* mark this file as already included (to prevent including it again) */
2151 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2155 /* read next line of input file */
2156 if (!fgets(line, MAX_LINE_LEN, file))
2159 /* check if line was completely read and is terminated by line break */
2160 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2163 /* cut trailing line break (this can be newline and/or carriage return) */
2164 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2165 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2168 /* copy raw input line for later use (mainly debugging output) */
2169 strcpy(line_raw, line);
2171 if (read_continued_line)
2174 /* !!! ??? WHY ??? !!! */
2175 /* cut leading whitespaces from input line */
2176 for (line_ptr = line; *line_ptr; line_ptr++)
2177 if (*line_ptr != ' ' && *line_ptr != '\t')
2181 /* append new line to existing line, if there is enough space */
2182 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2183 strcat(previous_line, line_ptr);
2185 strcpy(line, previous_line); /* copy storage buffer to line */
2187 read_continued_line = FALSE;
2190 /* if the last character is '\', continue at next line */
2191 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2193 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2194 strcpy(previous_line, line); /* copy line to storage buffer */
2196 read_continued_line = TRUE;
2201 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2202 line_raw, line_nr, FALSE))
2207 if (strEqual(token, "include"))
2209 if (getHashEntry(include_filename_hash, value) == NULL)
2211 char *basepath = getBasePath(filename);
2212 char *basename = getBaseName(value);
2213 char *filename_include = getPath2(basepath, basename);
2216 Error(ERR_INFO, "[including file '%s']", filename_include);
2219 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2223 free(filename_include);
2229 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2236 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2238 getHashEntry((SetupFileHash *)setup_file_data, token);
2240 if (old_value != NULL)
2242 if (!token_already_exists_warning)
2244 Error(ERR_INFO_LINE, "-");
2245 Error(ERR_WARN, "duplicate token(s) found in config file:");
2246 Error(ERR_INFO, "- config file: '%s'", filename);
2248 token_already_exists_warning = TRUE;
2251 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2252 Error(ERR_INFO, " old value: '%s'", old_value);
2253 Error(ERR_INFO, " new value: '%s'", value);
2257 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2261 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2271 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2272 if (token_value_separator_warning)
2273 Error(ERR_INFO_LINE, "-");
2276 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2277 if (token_already_exists_warning)
2278 Error(ERR_INFO_LINE, "-");
2281 if (token_count == 0 && include_count == 0)
2282 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2284 if (top_recursion_level)
2285 freeSetupFileHash(include_filename_hash);
2294 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2295 boolean top_recursion_level, boolean is_hash)
2297 static SetupFileHash *include_filename_hash = NULL;
2298 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2299 char *token, *value, *line_ptr;
2300 void *insert_ptr = NULL;
2301 boolean read_continued_line = FALSE;
2304 int token_count = 0;
2306 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2307 token_value_separator_warning = FALSE;
2310 if (!(file = fopen(filename, MODE_READ)))
2312 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2317 /* use "insert pointer" to store list end for constant insertion complexity */
2319 insert_ptr = setup_file_data;
2321 /* on top invocation, create hash to mark included files (to prevent loops) */
2322 if (top_recursion_level)
2323 include_filename_hash = newSetupFileHash();
2325 /* mark this file as already included (to prevent including it again) */
2326 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2330 /* read next line of input file */
2331 if (!fgets(line, MAX_LINE_LEN, file))
2334 /* check if line was completely read and is terminated by line break */
2335 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2338 /* cut trailing line break (this can be newline and/or carriage return) */
2339 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2340 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2343 /* copy raw input line for later use (mainly debugging output) */
2344 strcpy(line_raw, line);
2346 if (read_continued_line)
2348 /* cut leading whitespaces from input line */
2349 for (line_ptr = line; *line_ptr; line_ptr++)
2350 if (*line_ptr != ' ' && *line_ptr != '\t')
2353 /* append new line to existing line, if there is enough space */
2354 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2355 strcat(previous_line, line_ptr);
2357 strcpy(line, previous_line); /* copy storage buffer to line */
2359 read_continued_line = FALSE;
2362 /* if the last character is '\', continue at next line */
2363 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2365 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2366 strcpy(previous_line, line); /* copy line to storage buffer */
2368 read_continued_line = TRUE;
2373 /* cut trailing comment from input line */
2374 for (line_ptr = line; *line_ptr; line_ptr++)
2376 if (*line_ptr == '#')
2383 /* cut trailing whitespaces from input line */
2384 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2385 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2388 /* ignore empty lines */
2392 /* cut leading whitespaces from token */
2393 for (token = line; *token; token++)
2394 if (*token != ' ' && *token != '\t')
2397 /* start with empty value as reliable default */
2400 token_value_separator_found = FALSE;
2402 /* find end of token to determine start of value */
2403 for (line_ptr = token; *line_ptr; line_ptr++)
2406 /* first look for an explicit token/value separator, like ':' or '=' */
2407 if (*line_ptr == ':' || *line_ptr == '=')
2409 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2412 *line_ptr = '\0'; /* terminate token string */
2413 value = line_ptr + 1; /* set beginning of value */
2415 token_value_separator_found = TRUE;
2421 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2422 /* fallback: if no token/value separator found, also allow whitespaces */
2423 if (!token_value_separator_found)
2425 for (line_ptr = token; *line_ptr; line_ptr++)
2427 if (*line_ptr == ' ' || *line_ptr == '\t')
2429 *line_ptr = '\0'; /* terminate token string */
2430 value = line_ptr + 1; /* set beginning of value */
2432 token_value_separator_found = TRUE;
2438 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2439 if (token_value_separator_found)
2441 if (!token_value_separator_warning)
2443 Error(ERR_INFO_LINE, "-");
2444 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2445 Error(ERR_INFO, "- config file: '%s'", filename);
2447 token_value_separator_warning = TRUE;
2450 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2456 /* cut trailing whitespaces from token */
2457 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2458 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2461 /* cut leading whitespaces from value */
2462 for (; *value; value++)
2463 if (*value != ' ' && *value != '\t')
2468 value = "true"; /* treat tokens without value as "true" */
2473 if (strEqual(token, "include"))
2475 if (getHashEntry(include_filename_hash, value) == NULL)
2477 char *basepath = getBasePath(filename);
2478 char *basename = getBaseName(value);
2479 char *filename_include = getPath2(basepath, basename);
2482 Error(ERR_INFO, "[including file '%s']", filename_include);
2485 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2489 free(filename_include);
2493 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2499 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2501 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2510 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2511 if (token_value_separator_warning)
2512 Error(ERR_INFO_LINE, "-");
2515 if (token_count == 0)
2516 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2518 if (top_recursion_level)
2519 freeSetupFileHash(include_filename_hash);
2525 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2529 if (!(file = fopen(filename, MODE_WRITE)))
2531 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2536 BEGIN_HASH_ITERATION(hash, itr)
2538 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2539 HASH_ITERATION_VALUE(itr)));
2541 END_HASH_ITERATION(hash, itr)
2546 SetupFileList *loadSetupFileList(char *filename)
2548 SetupFileList *setup_file_list = newSetupFileList("", "");
2549 SetupFileList *first_valid_list_entry;
2551 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2553 freeSetupFileList(setup_file_list);
2558 first_valid_list_entry = setup_file_list->next;
2560 /* free empty list header */
2561 setup_file_list->next = NULL;
2562 freeSetupFileList(setup_file_list);
2564 return first_valid_list_entry;
2567 SetupFileHash *loadSetupFileHash(char *filename)
2569 SetupFileHash *setup_file_hash = newSetupFileHash();
2571 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2573 freeSetupFileHash(setup_file_hash);
2578 return setup_file_hash;
2581 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2582 char *filename, char *identifier)
2584 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2587 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2588 else if (!checkCookieString(value, identifier))
2589 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2593 /* ========================================================================= */
2594 /* setup file stuff */
2595 /* ========================================================================= */
2597 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2598 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2599 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2601 /* level directory info */
2602 #define LEVELINFO_TOKEN_IDENTIFIER 0
2603 #define LEVELINFO_TOKEN_NAME 1
2604 #define LEVELINFO_TOKEN_NAME_SORTING 2
2605 #define LEVELINFO_TOKEN_AUTHOR 3
2606 #define LEVELINFO_TOKEN_YEAR 4
2607 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2608 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2609 #define LEVELINFO_TOKEN_TESTED_BY 7
2610 #define LEVELINFO_TOKEN_LEVELS 8
2611 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2612 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2613 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2614 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2615 #define LEVELINFO_TOKEN_READONLY 13
2616 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2617 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2618 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2619 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2620 #define LEVELINFO_TOKEN_MUSIC_SET 18
2621 #define LEVELINFO_TOKEN_FILENAME 19
2622 #define LEVELINFO_TOKEN_FILETYPE 20
2623 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2624 #define LEVELINFO_TOKEN_HANDICAP 22
2625 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2627 #define NUM_LEVELINFO_TOKENS 24
2629 static LevelDirTree ldi;
2631 static struct TokenInfo levelinfo_tokens[] =
2633 /* level directory info */
2634 { TYPE_STRING, &ldi.identifier, "identifier" },
2635 { TYPE_STRING, &ldi.name, "name" },
2636 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2637 { TYPE_STRING, &ldi.author, "author" },
2638 { TYPE_STRING, &ldi.year, "year" },
2639 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2640 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2641 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2642 { TYPE_INTEGER, &ldi.levels, "levels" },
2643 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2644 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2645 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2646 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2647 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2648 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2649 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2650 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2651 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2652 { TYPE_STRING, &ldi.music_set, "music_set" },
2653 { TYPE_STRING, &ldi.level_filename, "filename" },
2654 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2655 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2656 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2657 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2660 static struct TokenInfo artworkinfo_tokens[] =
2662 /* artwork directory info */
2663 { TYPE_STRING, &ldi.identifier, "identifier" },
2664 { TYPE_STRING, &ldi.subdir, "subdir" },
2665 { TYPE_STRING, &ldi.name, "name" },
2666 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2667 { TYPE_STRING, &ldi.author, "author" },
2668 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2669 { TYPE_STRING, &ldi.basepath, "basepath" },
2670 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2671 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2672 { TYPE_INTEGER, &ldi.color, "color" },
2673 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2678 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2682 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2683 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2684 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2685 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2688 ti->node_parent = NULL;
2689 ti->node_group = NULL;
2696 ti->fullpath = NULL;
2697 ti->basepath = NULL;
2698 ti->identifier = NULL;
2699 ti->name = getStringCopy(ANONYMOUS_NAME);
2700 ti->name_sorting = NULL;
2701 ti->author = getStringCopy(ANONYMOUS_NAME);
2704 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2705 ti->latest_engine = FALSE; /* default: get from level */
2706 ti->parent_link = FALSE;
2707 ti->in_user_dir = FALSE;
2708 ti->user_defined = FALSE;
2710 ti->class_desc = NULL;
2712 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2714 if (ti->type == TREE_TYPE_LEVEL_DIR)
2716 ti->imported_from = NULL;
2717 ti->imported_by = NULL;
2718 ti->tested_by = NULL;
2720 ti->graphics_set_ecs = NULL;
2721 ti->graphics_set_aga = NULL;
2722 ti->graphics_set = NULL;
2723 ti->sounds_set = NULL;
2724 ti->music_set = NULL;
2725 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2726 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2727 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2729 ti->level_filename = NULL;
2730 ti->level_filetype = NULL;
2732 ti->special_flags = NULL;
2735 ti->first_level = 0;
2737 ti->level_group = FALSE;
2738 ti->handicap_level = 0;
2739 ti->readonly = TRUE;
2740 ti->handicap = TRUE;
2741 ti->skip_levels = FALSE;
2745 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2749 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2751 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2756 /* copy all values from the parent structure */
2758 ti->type = parent->type;
2760 ti->node_top = parent->node_top;
2761 ti->node_parent = parent;
2762 ti->node_group = NULL;
2769 ti->fullpath = NULL;
2770 ti->basepath = NULL;
2771 ti->identifier = NULL;
2772 ti->name = getStringCopy(ANONYMOUS_NAME);
2773 ti->name_sorting = NULL;
2774 ti->author = getStringCopy(parent->author);
2775 ti->year = getStringCopy(parent->year);
2777 ti->sort_priority = parent->sort_priority;
2778 ti->latest_engine = parent->latest_engine;
2779 ti->parent_link = FALSE;
2780 ti->in_user_dir = parent->in_user_dir;
2781 ti->user_defined = parent->user_defined;
2782 ti->color = parent->color;
2783 ti->class_desc = getStringCopy(parent->class_desc);
2785 ti->infotext = getStringCopy(parent->infotext);
2787 if (ti->type == TREE_TYPE_LEVEL_DIR)
2789 ti->imported_from = getStringCopy(parent->imported_from);
2790 ti->imported_by = getStringCopy(parent->imported_by);
2791 ti->tested_by = getStringCopy(parent->tested_by);
2793 ti->graphics_set_ecs = NULL;
2794 ti->graphics_set_aga = NULL;
2795 ti->graphics_set = NULL;
2796 ti->sounds_set = NULL;
2797 ti->music_set = NULL;
2798 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2799 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2800 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2802 ti->level_filename = NULL;
2803 ti->level_filetype = NULL;
2805 ti->special_flags = getStringCopy(parent->special_flags);
2808 ti->first_level = 0;
2810 ti->level_group = FALSE;
2811 ti->handicap_level = 0;
2813 ti->readonly = parent->readonly;
2815 ti->readonly = TRUE;
2817 ti->handicap = TRUE;
2818 ti->skip_levels = FALSE;
2822 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2824 TreeInfo *ti_copy = newTreeInfo();
2826 /* copy all values from the original structure */
2828 ti_copy->type = ti->type;
2830 ti_copy->node_top = ti->node_top;
2831 ti_copy->node_parent = ti->node_parent;
2832 ti_copy->node_group = ti->node_group;
2833 ti_copy->next = ti->next;
2835 ti_copy->cl_first = ti->cl_first;
2836 ti_copy->cl_cursor = ti->cl_cursor;
2838 ti_copy->subdir = getStringCopy(ti->subdir);
2839 ti_copy->fullpath = getStringCopy(ti->fullpath);
2840 ti_copy->basepath = getStringCopy(ti->basepath);
2841 ti_copy->identifier = getStringCopy(ti->identifier);
2842 ti_copy->name = getStringCopy(ti->name);
2843 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2844 ti_copy->author = getStringCopy(ti->author);
2845 ti_copy->year = getStringCopy(ti->year);
2846 ti_copy->imported_from = getStringCopy(ti->imported_from);
2847 ti_copy->imported_by = getStringCopy(ti->imported_by);
2848 ti_copy->tested_by = getStringCopy(ti->tested_by);
2850 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2851 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2852 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2853 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2854 ti_copy->music_set = getStringCopy(ti->music_set);
2855 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2856 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2857 ti_copy->music_path = getStringCopy(ti->music_path);
2859 ti_copy->level_filename = getStringCopy(ti->level_filename);
2860 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2862 ti_copy->special_flags = getStringCopy(ti->special_flags);
2864 ti_copy->levels = ti->levels;
2865 ti_copy->first_level = ti->first_level;
2866 ti_copy->last_level = ti->last_level;
2867 ti_copy->sort_priority = ti->sort_priority;
2869 ti_copy->latest_engine = ti->latest_engine;
2871 ti_copy->level_group = ti->level_group;
2872 ti_copy->parent_link = ti->parent_link;
2873 ti_copy->in_user_dir = ti->in_user_dir;
2874 ti_copy->user_defined = ti->user_defined;
2875 ti_copy->readonly = ti->readonly;
2876 ti_copy->handicap = ti->handicap;
2877 ti_copy->skip_levels = ti->skip_levels;
2879 ti_copy->color = ti->color;
2880 ti_copy->class_desc = getStringCopy(ti->class_desc);
2881 ti_copy->handicap_level = ti->handicap_level;
2883 ti_copy->infotext = getStringCopy(ti->infotext);
2888 void freeTreeInfo(TreeInfo *ti)
2893 checked_free(ti->subdir);
2894 checked_free(ti->fullpath);
2895 checked_free(ti->basepath);
2896 checked_free(ti->identifier);
2898 checked_free(ti->name);
2899 checked_free(ti->name_sorting);
2900 checked_free(ti->author);
2901 checked_free(ti->year);
2903 checked_free(ti->class_desc);
2905 checked_free(ti->infotext);
2907 if (ti->type == TREE_TYPE_LEVEL_DIR)
2909 checked_free(ti->imported_from);
2910 checked_free(ti->imported_by);
2911 checked_free(ti->tested_by);
2913 checked_free(ti->graphics_set_ecs);
2914 checked_free(ti->graphics_set_aga);
2915 checked_free(ti->graphics_set);
2916 checked_free(ti->sounds_set);
2917 checked_free(ti->music_set);
2919 checked_free(ti->graphics_path);
2920 checked_free(ti->sounds_path);
2921 checked_free(ti->music_path);
2923 checked_free(ti->level_filename);
2924 checked_free(ti->level_filetype);
2926 checked_free(ti->special_flags);
2932 void setSetupInfo(struct TokenInfo *token_info,
2933 int token_nr, char *token_value)
2935 int token_type = token_info[token_nr].type;
2936 void *setup_value = token_info[token_nr].value;
2938 if (token_value == NULL)
2941 /* set setup field to corresponding token value */
2946 *(boolean *)setup_value = get_boolean_from_string(token_value);
2950 *(int *)setup_value = get_switch3_from_string(token_value);
2954 *(Key *)setup_value = getKeyFromKeyName(token_value);
2958 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2962 *(int *)setup_value = get_integer_from_string(token_value);
2966 checked_free(*(char **)setup_value);
2967 *(char **)setup_value = getStringCopy(token_value);
2975 static int compareTreeInfoEntries(const void *object1, const void *object2)
2977 const TreeInfo *entry1 = *((TreeInfo **)object1);
2978 const TreeInfo *entry2 = *((TreeInfo **)object2);
2979 int class_sorting1 = 0, class_sorting2 = 0;
2982 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2984 class_sorting1 = LEVELSORTING(entry1);
2985 class_sorting2 = LEVELSORTING(entry2);
2987 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2988 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2989 entry1->type == TREE_TYPE_MUSIC_DIR)
2991 class_sorting1 = ARTWORKSORTING(entry1);
2992 class_sorting2 = ARTWORKSORTING(entry2);
2995 if (entry1->parent_link || entry2->parent_link)
2996 compare_result = (entry1->parent_link ? -1 : +1);
2997 else if (entry1->sort_priority == entry2->sort_priority)
2999 char *name1 = getStringToLower(entry1->name_sorting);
3000 char *name2 = getStringToLower(entry2->name_sorting);
3002 compare_result = strcmp(name1, name2);
3007 else if (class_sorting1 == class_sorting2)
3008 compare_result = entry1->sort_priority - entry2->sort_priority;
3010 compare_result = class_sorting1 - class_sorting2;
3012 return compare_result;
3015 static void createParentTreeInfoNode(TreeInfo *node_parent)
3019 if (node_parent == NULL)
3022 ti_new = newTreeInfo();
3023 setTreeInfoToDefaults(ti_new, node_parent->type);
3025 ti_new->node_parent = node_parent;
3026 ti_new->parent_link = TRUE;
3028 setString(&ti_new->identifier, node_parent->identifier);
3029 setString(&ti_new->name, ".. (parent directory)");
3030 setString(&ti_new->name_sorting, ti_new->name);
3032 setString(&ti_new->subdir, "..");
3033 setString(&ti_new->fullpath, node_parent->fullpath);
3035 ti_new->sort_priority = node_parent->sort_priority;
3036 ti_new->latest_engine = node_parent->latest_engine;
3038 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3040 pushTreeInfo(&node_parent->node_group, ti_new);
3044 /* -------------------------------------------------------------------------- */
3045 /* functions for handling level and custom artwork info cache */
3046 /* -------------------------------------------------------------------------- */
3048 static void LoadArtworkInfoCache()
3050 InitCacheDirectory();
3052 if (artworkinfo_cache_old == NULL)
3054 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3056 /* try to load artwork info hash from already existing cache file */
3057 artworkinfo_cache_old = loadSetupFileHash(filename);
3059 /* if no artwork info cache file was found, start with empty hash */
3060 if (artworkinfo_cache_old == NULL)
3061 artworkinfo_cache_old = newSetupFileHash();
3066 if (artworkinfo_cache_new == NULL)
3067 artworkinfo_cache_new = newSetupFileHash();
3070 static void SaveArtworkInfoCache()
3072 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3074 InitCacheDirectory();
3076 saveSetupFileHash(artworkinfo_cache_new, filename);
3081 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3083 static char *prefix = NULL;
3085 checked_free(prefix);
3087 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3092 /* (identical to above function, but separate string buffer needed -- nasty) */
3093 static char *getCacheToken(char *prefix, char *suffix)
3095 static char *token = NULL;
3097 checked_free(token);
3099 token = getStringCat2WithSeparator(prefix, suffix, ".");
3104 static char *getFileTimestampString(char *filename)
3107 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3109 struct stat file_status;
3111 if (stat(filename, &file_status) != 0) /* cannot stat file */
3112 return getStringCopy(i_to_a(0));
3114 return getStringCopy(i_to_a(file_status.st_mtime));
3118 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3120 struct stat file_status;
3122 if (timestamp_string == NULL)
3125 if (stat(filename, &file_status) != 0) /* cannot stat file */
3128 return (file_status.st_mtime != atoi(timestamp_string));
3131 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3133 char *identifier = level_node->subdir;
3134 char *type_string = ARTWORK_DIRECTORY(type);
3135 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3136 char *token_main = getCacheToken(token_prefix, "CACHED");
3137 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3138 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3139 TreeInfo *artwork_info = NULL;
3141 if (!use_artworkinfo_cache)
3148 artwork_info = newTreeInfo();
3149 setTreeInfoToDefaults(artwork_info, type);
3151 /* set all structure fields according to the token/value pairs */
3152 ldi = *artwork_info;
3153 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3155 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3156 char *value = getHashEntry(artworkinfo_cache_old, token);
3158 setSetupInfo(artworkinfo_tokens, i, value);
3160 /* check if cache entry for this item is invalid or incomplete */
3164 Error(ERR_WARN, "cache entry '%s' invalid", token);
3171 *artwork_info = ldi;
3176 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3177 LEVELINFO_FILENAME);
3178 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3179 ARTWORKINFO_FILENAME(type));
3181 /* check if corresponding "levelinfo.conf" file has changed */
3182 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3183 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3185 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3188 /* check if corresponding "<artworkinfo>.conf" file has changed */
3189 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3190 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3192 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3197 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
3200 checked_free(filename_levelinfo);
3201 checked_free(filename_artworkinfo);
3204 if (!cached && artwork_info != NULL)
3206 freeTreeInfo(artwork_info);
3211 return artwork_info;
3214 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3215 LevelDirTree *level_node, int type)
3217 char *identifier = level_node->subdir;
3218 char *type_string = ARTWORK_DIRECTORY(type);
3219 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3220 char *token_main = getCacheToken(token_prefix, "CACHED");
3221 boolean set_cache_timestamps = TRUE;
3224 setHashEntry(artworkinfo_cache_new, token_main, "true");
3226 if (set_cache_timestamps)
3228 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3229 LEVELINFO_FILENAME);
3230 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3231 ARTWORKINFO_FILENAME(type));
3232 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3233 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3235 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3236 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3238 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3239 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3241 checked_free(filename_levelinfo);
3242 checked_free(filename_artworkinfo);
3243 checked_free(timestamp_levelinfo);
3244 checked_free(timestamp_artworkinfo);
3247 ldi = *artwork_info;
3248 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3250 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3251 char *value = getSetupValue(artworkinfo_tokens[i].type,
3252 artworkinfo_tokens[i].value);
3254 setHashEntry(artworkinfo_cache_new, token, value);
3259 /* -------------------------------------------------------------------------- */
3260 /* functions for loading level info and custom artwork info */
3261 /* -------------------------------------------------------------------------- */
3263 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3264 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3266 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3267 TreeInfo *node_parent,
3268 char *level_directory,
3269 char *directory_name)
3272 static unsigned int progress_delay = 0;
3273 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3275 char *directory_path = getPath2(level_directory, directory_name);
3276 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3277 SetupFileHash *setup_file_hash;
3278 LevelDirTree *leveldir_new = NULL;
3281 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3282 if (!options.debug && !fileExists(filename))
3284 free(directory_path);
3290 setup_file_hash = loadSetupFileHash(filename);
3292 if (setup_file_hash == NULL)
3294 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3296 free(directory_path);
3302 leveldir_new = newTreeInfo();
3305 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3307 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3309 leveldir_new->subdir = getStringCopy(directory_name);
3311 checkSetupFileHashIdentifier(setup_file_hash, filename,
3312 getCookie("LEVELINFO"));
3314 /* set all structure fields according to the token/value pairs */
3315 ldi = *leveldir_new;
3316 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3317 setSetupInfo(levelinfo_tokens, i,
3318 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3319 *leveldir_new = ldi;
3321 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3322 setString(&leveldir_new->name, leveldir_new->subdir);
3324 if (leveldir_new->identifier == NULL)
3325 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3327 if (leveldir_new->name_sorting == NULL)
3328 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3330 if (node_parent == NULL) /* top level group */
3332 leveldir_new->basepath = getStringCopy(level_directory);
3333 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3335 else /* sub level group */
3337 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3338 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3342 if (leveldir_new->levels < 1)
3343 leveldir_new->levels = 1;
3346 leveldir_new->last_level =
3347 leveldir_new->first_level + leveldir_new->levels - 1;
3349 leveldir_new->in_user_dir =
3350 (!strEqual(leveldir_new->basepath, options.level_directory));
3353 printf("::: '%s' -> %d\n",
3354 leveldir_new->identifier,
3355 leveldir_new->in_user_dir);
3358 /* adjust some settings if user's private level directory was detected */
3359 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3360 leveldir_new->in_user_dir &&
3361 (strEqual(leveldir_new->subdir, getLoginName()) ||
3362 strEqual(leveldir_new->name, getLoginName()) ||
3363 strEqual(leveldir_new->author, getRealName())))
3365 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3366 leveldir_new->readonly = FALSE;
3369 leveldir_new->user_defined =
3370 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3372 leveldir_new->color = LEVELCOLOR(leveldir_new);
3374 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3376 leveldir_new->handicap_level = /* set handicap to default value */
3377 (leveldir_new->user_defined || !leveldir_new->handicap ?
3378 leveldir_new->last_level : leveldir_new->first_level);
3382 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3383 leveldir_new->level_group);
3385 if (leveldir_new->level_group ||
3386 DelayReached(&progress_delay, progress_delay_value))
3387 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3390 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3394 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3396 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3398 /* skip level sets without levels (which are probably artwork base sets) */
3400 freeSetupFileHash(setup_file_hash);
3401 free(directory_path);
3409 pushTreeInfo(node_first, leveldir_new);
3411 freeSetupFileHash(setup_file_hash);
3413 if (leveldir_new->level_group)
3415 /* create node to link back to current level directory */
3416 createParentTreeInfoNode(leveldir_new);
3418 /* recursively step into sub-directory and look for more level series */
3419 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3420 leveldir_new, directory_path);
3423 free(directory_path);
3430 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3431 TreeInfo *node_parent,
3432 char *level_directory)
3435 DirectoryEntry *dir_entry;
3436 boolean valid_entry_found = FALSE;
3439 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3442 if ((dir = openDirectory(level_directory)) == NULL)
3444 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3450 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3453 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3455 char *directory_name = dir_entry->basename;
3456 char *directory_path = getPath2(level_directory, directory_name);
3459 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3462 /* skip entries for current and parent directory */
3463 if (strEqual(directory_name, ".") ||
3464 strEqual(directory_name, ".."))
3466 free(directory_path);
3472 /* find out if directory entry is itself a directory */
3473 if (!dir_entry->is_directory) /* not a directory */
3475 free(directory_path);
3478 Error(ERR_INFO, "* entry '%s' is not a directory ...", directory_name);
3484 /* find out if directory entry is itself a directory */
3485 struct stat file_status;
3486 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3487 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3489 free(directory_path);
3495 free(directory_path);
3497 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3498 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3499 strEqual(directory_name, MUSIC_DIRECTORY))
3502 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3507 closeDirectory(dir);
3509 /* special case: top level directory may directly contain "levelinfo.conf" */
3510 if (node_parent == NULL && !valid_entry_found)
3512 /* check if this directory directly contains a file "levelinfo.conf" */
3513 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3514 level_directory, ".");
3517 if (!valid_entry_found)
3518 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3524 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3525 TreeInfo *node_parent,
3526 char *level_directory)
3529 struct dirent *dir_entry;
3530 boolean valid_entry_found = FALSE;
3533 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3536 if ((dir = opendir(level_directory)) == NULL)
3538 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3544 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3547 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3549 struct stat file_status;
3550 char *directory_name = dir_entry->d_name;
3551 char *directory_path = getPath2(level_directory, directory_name);
3554 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3557 /* skip entries for current and parent directory */
3558 if (strEqual(directory_name, ".") ||
3559 strEqual(directory_name, ".."))
3561 free(directory_path);
3565 /* find out if directory entry is itself a directory */
3566 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3567 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3569 free(directory_path);
3573 free(directory_path);
3575 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3576 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3577 strEqual(directory_name, MUSIC_DIRECTORY))
3580 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3587 /* special case: top level directory may directly contain "levelinfo.conf" */
3588 if (node_parent == NULL && !valid_entry_found)
3590 /* check if this directory directly contains a file "levelinfo.conf" */
3591 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3592 level_directory, ".");
3595 if (!valid_entry_found)
3596 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3601 boolean AdjustGraphicsForEMC()
3603 boolean settings_changed = FALSE;
3605 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3606 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3608 return settings_changed;
3611 void LoadLevelInfo()
3613 InitUserLevelDirectory(getLoginName());
3615 DrawInitText("Loading level series", 120, FC_GREEN);
3617 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3618 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3620 /* after loading all level set information, clone the level directory tree
3621 and remove all level sets without levels (these may still contain artwork
3622 to be offered in the setup menu as "custom artwork", and are therefore
3623 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3624 leveldir_first_all = leveldir_first;
3625 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3627 AdjustGraphicsForEMC();
3629 /* before sorting, the first entries will be from the user directory */
3630 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3632 if (leveldir_first == NULL)
3633 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3635 sortTreeInfo(&leveldir_first);
3638 dumpTreeInfo(leveldir_first, 0);
3644 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3645 TreeInfo *node_parent,
3646 char *base_directory,
3647 char *directory_name, int type)
3649 char *directory_path = getPath2(base_directory, directory_name);
3650 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3651 SetupFileHash *setup_file_hash = NULL;
3652 TreeInfo *artwork_new = NULL;
3655 if (fileExists(filename))
3656 setup_file_hash = loadSetupFileHash(filename);
3658 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3661 DirectoryEntry *dir_entry;
3662 boolean valid_file_found = FALSE;
3664 if ((dir = openDirectory(directory_path)) != NULL)
3666 while ((dir_entry = readDirectory(dir)) != NULL)
3668 char *entry_name = dir_entry->basename;
3670 if (FileIsArtworkType(entry_name, type))
3672 valid_file_found = TRUE;
3678 closeDirectory(dir);
3681 if (!valid_file_found)
3683 if (!strEqual(directory_name, "."))
3684 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3686 free(directory_path);
3693 artwork_new = newTreeInfo();
3696 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3698 setTreeInfoToDefaults(artwork_new, type);
3700 artwork_new->subdir = getStringCopy(directory_name);
3702 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3705 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3708 /* set all structure fields according to the token/value pairs */
3710 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3711 setSetupInfo(levelinfo_tokens, i,
3712 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3715 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3716 setString(&artwork_new->name, artwork_new->subdir);
3718 if (artwork_new->identifier == NULL)
3719 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3721 if (artwork_new->name_sorting == NULL)
3722 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3725 if (node_parent == NULL) /* top level group */
3727 artwork_new->basepath = getStringCopy(base_directory);
3728 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3730 else /* sub level group */
3732 artwork_new->basepath = getStringCopy(node_parent->basepath);
3733 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3736 artwork_new->in_user_dir =
3737 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3739 /* (may use ".sort_priority" from "setup_file_hash" above) */
3740 artwork_new->color = ARTWORKCOLOR(artwork_new);
3742 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3744 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3746 if (strEqual(artwork_new->subdir, "."))
3748 if (artwork_new->user_defined)
3750 setString(&artwork_new->identifier, "private");
3751 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3755 setString(&artwork_new->identifier, "classic");
3756 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3759 /* set to new values after changing ".sort_priority" */
3760 artwork_new->color = ARTWORKCOLOR(artwork_new);
3762 setString(&artwork_new->class_desc,
3763 getLevelClassDescription(artwork_new));
3767 setString(&artwork_new->identifier, artwork_new->subdir);
3770 setString(&artwork_new->name, artwork_new->identifier);
3771 setString(&artwork_new->name_sorting, artwork_new->name);
3775 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3778 pushTreeInfo(node_first, artwork_new);
3780 freeSetupFileHash(setup_file_hash);
3782 free(directory_path);
3790 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3791 TreeInfo *node_parent,
3792 char *base_directory,
3793 char *directory_name, int type)
3795 char *directory_path = getPath2(base_directory, directory_name);
3796 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3797 SetupFileHash *setup_file_hash = NULL;
3798 TreeInfo *artwork_new = NULL;
3801 if (fileExists(filename))
3802 setup_file_hash = loadSetupFileHash(filename);
3804 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3807 struct dirent *dir_entry;
3808 boolean valid_file_found = FALSE;
3810 if ((dir = opendir(directory_path)) != NULL)
3812 while ((dir_entry = readdir(dir)) != NULL)
3814 char *entry_name = dir_entry->d_name;
3816 if (FileIsArtworkType(entry_name, type))
3818 valid_file_found = TRUE;
3826 if (!valid_file_found)
3828 if (!strEqual(directory_name, "."))
3829 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3831 free(directory_path);
3838 artwork_new = newTreeInfo();
3841 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3843 setTreeInfoToDefaults(artwork_new, type);
3845 artwork_new->subdir = getStringCopy(directory_name);
3847 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3850 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3853 /* set all structure fields according to the token/value pairs */
3855 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3856 setSetupInfo(levelinfo_tokens, i,
3857 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3860 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3861 setString(&artwork_new->name, artwork_new->subdir);
3863 if (artwork_new->identifier == NULL)
3864 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3866 if (artwork_new->name_sorting == NULL)
3867 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3870 if (node_parent == NULL) /* top level group */
3872 artwork_new->basepath = getStringCopy(base_directory);
3873 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3875 else /* sub level group */
3877 artwork_new->basepath = getStringCopy(node_parent->basepath);
3878 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3881 artwork_new->in_user_dir =
3882 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3884 /* (may use ".sort_priority" from "setup_file_hash" above) */
3885 artwork_new->color = ARTWORKCOLOR(artwork_new);
3887 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3889 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3891 if (strEqual(artwork_new->subdir, "."))
3893 if (artwork_new->user_defined)
3895 setString(&artwork_new->identifier, "private");
3896 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3900 setString(&artwork_new->identifier, "classic");
3901 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3904 /* set to new values after changing ".sort_priority" */
3905 artwork_new->color = ARTWORKCOLOR(artwork_new);
3907 setString(&artwork_new->class_desc,
3908 getLevelClassDescription(artwork_new));
3912 setString(&artwork_new->identifier, artwork_new->subdir);
3915 setString(&artwork_new->name, artwork_new->identifier);
3916 setString(&artwork_new->name_sorting, artwork_new->name);
3920 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3923 pushTreeInfo(node_first, artwork_new);
3925 freeSetupFileHash(setup_file_hash);
3927 free(directory_path);
3937 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3938 TreeInfo *node_parent,
3939 char *base_directory, int type)
3942 DirectoryEntry *dir_entry;
3943 boolean valid_entry_found = FALSE;
3945 if ((dir = openDirectory(base_directory)) == NULL)
3947 /* display error if directory is main "options.graphics_directory" etc. */
3948 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3949 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3954 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3956 char *directory_name = dir_entry->basename;
3957 char *directory_path = getPath2(base_directory, directory_name);
3959 /* skip directory entries for current and parent directory */
3960 if (strEqual(directory_name, ".") ||
3961 strEqual(directory_name, ".."))
3963 free(directory_path);
3969 /* skip directory entries which are not a directory */
3970 if (!dir_entry->is_directory) /* not a directory */
3972 free(directory_path);
3977 /* skip directory entries which are not a directory or are not accessible */
3978 struct stat file_status;
3979 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3980 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3982 free(directory_path);
3988 free(directory_path);
3990 /* check if this directory contains artwork with or without config file */
3991 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3993 directory_name, type);
3996 closeDirectory(dir);
3998 /* check if this directory directly contains artwork itself */
3999 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4000 base_directory, ".",
4002 if (!valid_entry_found)
4003 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4009 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4010 TreeInfo *node_parent,
4011 char *base_directory, int type)
4014 struct dirent *dir_entry;
4015 boolean valid_entry_found = FALSE;
4017 if ((dir = opendir(base_directory)) == NULL)
4019 /* display error if directory is main "options.graphics_directory" etc. */
4020 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4021 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
4026 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
4028 struct stat file_status;
4029 char *directory_name = dir_entry->d_name;
4030 char *directory_path = getPath2(base_directory, directory_name);
4032 /* skip directory entries for current and parent directory */
4033 if (strEqual(directory_name, ".") ||
4034 strEqual(directory_name, ".."))
4036 free(directory_path);
4040 /* skip directory entries which are not a directory or are not accessible */
4041 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
4042 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
4044 free(directory_path);
4048 free(directory_path);
4050 /* check if this directory contains artwork with or without config file */
4051 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4053 directory_name, type);
4058 /* check if this directory directly contains artwork itself */
4059 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4060 base_directory, ".",
4062 if (!valid_entry_found)
4063 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4069 static TreeInfo *getDummyArtworkInfo(int type)
4071 /* this is only needed when there is completely no artwork available */
4072 TreeInfo *artwork_new = newTreeInfo();
4074 setTreeInfoToDefaults(artwork_new, type);
4076 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4077 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4078 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4080 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4081 setString(&artwork_new->name, UNDEFINED_FILENAME);
4082 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4087 void LoadArtworkInfo()
4089 LoadArtworkInfoCache();
4091 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
4093 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4094 options.graphics_directory,
4095 TREE_TYPE_GRAPHICS_DIR);
4096 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4097 getUserGraphicsDir(),
4098 TREE_TYPE_GRAPHICS_DIR);
4100 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4101 options.sounds_directory,
4102 TREE_TYPE_SOUNDS_DIR);
4103 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4105 TREE_TYPE_SOUNDS_DIR);
4107 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4108 options.music_directory,
4109 TREE_TYPE_MUSIC_DIR);
4110 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4112 TREE_TYPE_MUSIC_DIR);
4114 if (artwork.gfx_first == NULL)
4115 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4116 if (artwork.snd_first == NULL)
4117 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4118 if (artwork.mus_first == NULL)
4119 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4121 /* before sorting, the first entries will be from the user directory */
4122 artwork.gfx_current =
4123 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4124 if (artwork.gfx_current == NULL)
4125 artwork.gfx_current =
4126 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4127 if (artwork.gfx_current == NULL)
4128 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4130 artwork.snd_current =
4131 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4132 if (artwork.snd_current == NULL)
4133 artwork.snd_current =
4134 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4135 if (artwork.snd_current == NULL)
4136 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4138 artwork.mus_current =
4139 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4140 if (artwork.mus_current == NULL)
4141 artwork.mus_current =
4142 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4143 if (artwork.mus_current == NULL)
4144 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4146 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4147 artwork.snd_current_identifier = artwork.snd_current->identifier;
4148 artwork.mus_current_identifier = artwork.mus_current->identifier;
4151 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
4152 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
4153 printf("music set == %s\n\n", artwork.mus_current_identifier);
4156 sortTreeInfo(&artwork.gfx_first);
4157 sortTreeInfo(&artwork.snd_first);
4158 sortTreeInfo(&artwork.mus_first);
4161 dumpTreeInfo(artwork.gfx_first, 0);
4162 dumpTreeInfo(artwork.snd_first, 0);
4163 dumpTreeInfo(artwork.mus_first, 0);
4167 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
4168 LevelDirTree *level_node)
4171 static unsigned int progress_delay = 0;
4172 unsigned int progress_delay_value = 100; /* (in milliseconds) */
4174 int type = (*artwork_node)->type;
4176 /* recursively check all level directories for artwork sub-directories */
4180 /* check all tree entries for artwork, but skip parent link entries */
4181 if (!level_node->parent_link)
4183 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4184 boolean cached = (artwork_new != NULL);
4188 pushTreeInfo(artwork_node, artwork_new);
4192 TreeInfo *topnode_last = *artwork_node;
4193 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4194 ARTWORK_DIRECTORY(type));
4196 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4198 if (topnode_last != *artwork_node) /* check for newly added node */
4200 artwork_new = *artwork_node;
4202 setString(&artwork_new->identifier, level_node->subdir);
4203 setString(&artwork_new->name, level_node->name);
4204 setString(&artwork_new->name_sorting, level_node->name_sorting);
4206 artwork_new->sort_priority = level_node->sort_priority;
4207 artwork_new->color = LEVELCOLOR(artwork_new);
4213 /* insert artwork info (from old cache or filesystem) into new cache */
4214 if (artwork_new != NULL)
4215 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4219 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
4220 level_node->level_group);
4222 if (level_node->level_group ||
4223 DelayReached(&progress_delay, progress_delay_value))
4224 DrawInitText(level_node->name, 150, FC_YELLOW);
4227 if (level_node->node_group != NULL)
4228 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
4230 level_node = level_node->next;
4234 void LoadLevelArtworkInfo()
4236 print_timestamp_init("LoadLevelArtworkInfo");
4238 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4240 print_timestamp_time("DrawTimeText");
4242 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
4243 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4244 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
4245 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4246 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
4247 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4249 SaveArtworkInfoCache();
4251 print_timestamp_time("SaveArtworkInfoCache");
4253 /* needed for reloading level artwork not known at ealier stage */
4255 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
4257 artwork.gfx_current =
4258 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4259 if (artwork.gfx_current == NULL)
4260 artwork.gfx_current =
4261 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4262 if (artwork.gfx_current == NULL)
4263 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4266 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
4268 artwork.snd_current =
4269 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4270 if (artwork.snd_current == NULL)
4271 artwork.snd_current =
4272 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4273 if (artwork.snd_current == NULL)
4274 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4277 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
4279 artwork.mus_current =
4280 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4281 if (artwork.mus_current == NULL)
4282 artwork.mus_current =
4283 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4284 if (artwork.mus_current == NULL)
4285 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4288 print_timestamp_time("getTreeInfoFromIdentifier");
4290 sortTreeInfo(&artwork.gfx_first);
4291 sortTreeInfo(&artwork.snd_first);
4292 sortTreeInfo(&artwork.mus_first);
4294 print_timestamp_time("sortTreeInfo");
4297 dumpTreeInfo(artwork.gfx_first, 0);
4298 dumpTreeInfo(artwork.snd_first, 0);
4299 dumpTreeInfo(artwork.mus_first, 0);
4302 print_timestamp_done("LoadLevelArtworkInfo");
4305 static void SaveUserLevelInfo()
4307 LevelDirTree *level_info;
4312 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
4314 if (!(file = fopen(filename, MODE_WRITE)))
4316 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4321 level_info = newTreeInfo();
4323 /* always start with reliable default values */
4324 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4326 setString(&level_info->name, getLoginName());
4327 setString(&level_info->author, getRealName());
4328 level_info->levels = 100;
4329 level_info->first_level = 1;
4331 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4333 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4334 getCookie("LEVELINFO")));
4337 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4339 if (i == LEVELINFO_TOKEN_NAME ||
4340 i == LEVELINFO_TOKEN_AUTHOR ||
4341 i == LEVELINFO_TOKEN_LEVELS ||
4342 i == LEVELINFO_TOKEN_FIRST_LEVEL)
4343 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4345 /* just to make things nicer :) */
4346 if (i == LEVELINFO_TOKEN_AUTHOR)
4347 fprintf(file, "\n");
4350 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4354 SetFilePermissions(filename, PERMS_PRIVATE);
4356 freeTreeInfo(level_info);
4360 char *getSetupValue(int type, void *value)
4362 static char value_string[MAX_LINE_LEN];
4370 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4374 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4378 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4379 *(int *)value == FALSE ? "off" : "on"));
4383 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4386 case TYPE_YES_NO_AUTO:
4387 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4388 *(int *)value == FALSE ? "no" : "yes"));
4392 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4396 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4400 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4404 sprintf(value_string, "%d", *(int *)value);
4408 if (*(char **)value == NULL)
4411 strcpy(value_string, *(char **)value);
4415 value_string[0] = '\0';
4419 if (type & TYPE_GHOSTED)
4420 strcpy(value_string, "n/a");
4422 return value_string;
4425 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4429 static char token_string[MAX_LINE_LEN];
4430 int token_type = token_info[token_nr].type;
4431 void *setup_value = token_info[token_nr].value;
4432 char *token_text = token_info[token_nr].text;
4433 char *value_string = getSetupValue(token_type, setup_value);
4435 /* build complete token string */
4436 sprintf(token_string, "%s%s", prefix, token_text);
4438 /* build setup entry line */
4439 line = getFormattedSetupEntry(token_string, value_string);
4441 if (token_type == TYPE_KEY_X11)
4443 Key key = *(Key *)setup_value;
4444 char *keyname = getKeyNameFromKey(key);
4446 /* add comment, if useful */
4447 if (!strEqual(keyname, "(undefined)") &&
4448 !strEqual(keyname, "(unknown)"))
4450 /* add at least one whitespace */
4452 for (i = strlen(line); i < token_comment_position; i++)
4456 strcat(line, keyname);
4463 void LoadLevelSetup_LastSeries()
4465 /* ----------------------------------------------------------------------- */
4466 /* ~/.<program>/levelsetup.conf */
4467 /* ----------------------------------------------------------------------- */
4469 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4470 SetupFileHash *level_setup_hash = NULL;
4472 /* always start with reliable default values */
4473 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4475 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
4476 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4478 if (leveldir_current == NULL)
4479 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4482 if ((level_setup_hash = loadSetupFileHash(filename)))
4484 char *last_level_series =
4485 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4487 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4489 if (leveldir_current == NULL)
4490 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4492 checkSetupFileHashIdentifier(level_setup_hash, filename,
4493 getCookie("LEVELSETUP"));
4495 freeSetupFileHash(level_setup_hash);
4498 Error(ERR_WARN, "using default setup values");
4503 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4505 /* ----------------------------------------------------------------------- */
4506 /* ~/.<program>/levelsetup.conf */
4507 /* ----------------------------------------------------------------------- */
4509 // check if the current level directory structure is available at this point
4510 if (leveldir_current == NULL)
4513 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4514 char *level_subdir = leveldir_current->subdir;
4517 InitUserDataDirectory();
4519 if (!(file = fopen(filename, MODE_WRITE)))
4521 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4528 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4529 getCookie("LEVELSETUP")));
4531 if (deactivate_last_level_series)
4532 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4534 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4539 SetFilePermissions(filename, PERMS_PRIVATE);
4544 void SaveLevelSetup_LastSeries()
4546 SaveLevelSetup_LastSeries_Ext(FALSE);
4549 void SaveLevelSetup_LastSeries_Deactivate()
4551 SaveLevelSetup_LastSeries_Ext(TRUE);
4556 static void checkSeriesInfo()
4558 static char *level_directory = NULL;
4561 DirectoryEntry *dir_entry;
4564 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4566 level_directory = getPath2((leveldir_current->in_user_dir ?
4567 getUserLevelDir(NULL) :
4568 options.level_directory),
4569 leveldir_current->fullpath);
4571 if ((dir = openDirectory(level_directory)) == NULL)
4573 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4579 while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */
4581 if (strlen(dir_entry->basename) > 4 &&
4582 dir_entry->basename[3] == '.' &&
4583 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4585 char levelnum_str[4];
4588 strncpy(levelnum_str, dir_entry->basename, 3);
4589 levelnum_str[3] = '\0';
4591 levelnum_value = atoi(levelnum_str);
4593 if (levelnum_value < leveldir_current->first_level)
4595 Error(ERR_WARN, "additional level %d found", levelnum_value);
4596 leveldir_current->first_level = levelnum_value;
4598 else if (levelnum_value > leveldir_current->last_level)
4600 Error(ERR_WARN, "additional level %d found", levelnum_value);
4601 leveldir_current->last_level = levelnum_value;
4607 closeDirectory(dir);
4612 static void checkSeriesInfo()
4614 static char *level_directory = NULL;
4617 struct dirent *dir_entry;
4620 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4622 level_directory = getPath2((leveldir_current->in_user_dir ?
4623 getUserLevelDir(NULL) :
4624 options.level_directory),
4625 leveldir_current->fullpath);
4627 if ((dir = opendir(level_directory)) == NULL)
4629 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4635 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4637 if (strlen(dir_entry->d_name) > 4 &&
4638 dir_entry->d_name[3] == '.' &&
4639 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4641 char levelnum_str[4];
4644 strncpy(levelnum_str, dir_entry->d_name, 3);
4645 levelnum_str[3] = '\0';
4647 levelnum_value = atoi(levelnum_str);
4649 if (levelnum_value < leveldir_current->first_level)
4651 Error(ERR_WARN, "additional level %d found", levelnum_value);
4652 leveldir_current->first_level = levelnum_value;
4654 else if (levelnum_value > leveldir_current->last_level)
4656 Error(ERR_WARN, "additional level %d found", levelnum_value);
4657 leveldir_current->last_level = levelnum_value;
4668 void LoadLevelSetup_SeriesInfo()
4671 SetupFileHash *level_setup_hash = NULL;
4672 char *level_subdir = leveldir_current->subdir;
4675 /* always start with reliable default values */
4676 level_nr = leveldir_current->first_level;
4678 for (i = 0; i < MAX_LEVELS; i++)
4680 LevelStats_setPlayed(i, 0);
4681 LevelStats_setSolved(i, 0);
4684 checkSeriesInfo(leveldir_current);
4686 /* ----------------------------------------------------------------------- */
4687 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4688 /* ----------------------------------------------------------------------- */
4690 level_subdir = leveldir_current->subdir;
4692 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4694 if ((level_setup_hash = loadSetupFileHash(filename)))
4698 /* get last played level in this level set */
4700 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4704 level_nr = atoi(token_value);
4706 if (level_nr < leveldir_current->first_level)
4707 level_nr = leveldir_current->first_level;
4708 if (level_nr > leveldir_current->last_level)
4709 level_nr = leveldir_current->last_level;
4712 /* get handicap level in this level set */
4714 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4718 int level_nr = atoi(token_value);
4720 if (level_nr < leveldir_current->first_level)
4721 level_nr = leveldir_current->first_level;
4722 if (level_nr > leveldir_current->last_level + 1)
4723 level_nr = leveldir_current->last_level;
4725 if (leveldir_current->user_defined || !leveldir_current->handicap)
4726 level_nr = leveldir_current->last_level;
4728 leveldir_current->handicap_level = level_nr;
4731 /* get number of played and solved levels in this level set */
4733 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4735 char *token = HASH_ITERATION_TOKEN(itr);
4736 char *value = HASH_ITERATION_VALUE(itr);
4738 if (strlen(token) == 3 &&
4739 token[0] >= '0' && token[0] <= '9' &&
4740 token[1] >= '0' && token[1] <= '9' &&
4741 token[2] >= '0' && token[2] <= '9')
4743 int level_nr = atoi(token);
4746 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4748 value = strchr(value, ' ');
4751 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4754 END_HASH_ITERATION(hash, itr)
4756 checkSetupFileHashIdentifier(level_setup_hash, filename,
4757 getCookie("LEVELSETUP"));
4759 freeSetupFileHash(level_setup_hash);
4762 Error(ERR_WARN, "using default setup values");
4767 void SaveLevelSetup_SeriesInfo()
4770 char *level_subdir = leveldir_current->subdir;
4771 char *level_nr_str = int2str(level_nr, 0);
4772 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4776 /* ----------------------------------------------------------------------- */
4777 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4778 /* ----------------------------------------------------------------------- */
4780 InitLevelSetupDirectory(level_subdir);
4782 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4784 if (!(file = fopen(filename, MODE_WRITE)))
4786 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4791 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4792 getCookie("LEVELSETUP")));
4793 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4795 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4796 handicap_level_str));
4798 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4801 if (LevelStats_getPlayed(i) > 0 ||
4802 LevelStats_getSolved(i) > 0)
4807 sprintf(token, "%03d", i);
4808 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4810 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4816 SetFilePermissions(filename, PERMS_PRIVATE);
4821 int LevelStats_getPlayed(int nr)
4823 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4826 int LevelStats_getSolved(int nr)
4828 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4831 void LevelStats_setPlayed(int nr, int value)
4833 if (nr >= 0 && nr < MAX_LEVELS)
4834 level_stats[nr].played = value;
4837 void LevelStats_setSolved(int nr, int value)
4839 if (nr >= 0 && nr < MAX_LEVELS)
4840 level_stats[nr].solved = value;
4843 void LevelStats_incPlayed(int nr)
4845 if (nr >= 0 && nr < MAX_LEVELS)
4846 level_stats[nr].played++;
4849 void LevelStats_incSolved(int nr)
4851 if (nr >= 0 && nr < MAX_LEVELS)
4852 level_stats[nr].solved++;