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);
2929 // recursively free child node
2931 freeTreeInfo(ti->node_group);
2933 // recursively free next node
2935 freeTreeInfo(ti->next);
2940 void setSetupInfo(struct TokenInfo *token_info,
2941 int token_nr, char *token_value)
2943 int token_type = token_info[token_nr].type;
2944 void *setup_value = token_info[token_nr].value;
2946 if (token_value == NULL)
2949 /* set setup field to corresponding token value */
2954 *(boolean *)setup_value = get_boolean_from_string(token_value);
2958 *(int *)setup_value = get_switch3_from_string(token_value);
2962 *(Key *)setup_value = getKeyFromKeyName(token_value);
2966 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2970 *(int *)setup_value = get_integer_from_string(token_value);
2974 checked_free(*(char **)setup_value);
2975 *(char **)setup_value = getStringCopy(token_value);
2983 static int compareTreeInfoEntries(const void *object1, const void *object2)
2985 const TreeInfo *entry1 = *((TreeInfo **)object1);
2986 const TreeInfo *entry2 = *((TreeInfo **)object2);
2987 int class_sorting1 = 0, class_sorting2 = 0;
2990 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2992 class_sorting1 = LEVELSORTING(entry1);
2993 class_sorting2 = LEVELSORTING(entry2);
2995 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2996 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2997 entry1->type == TREE_TYPE_MUSIC_DIR)
2999 class_sorting1 = ARTWORKSORTING(entry1);
3000 class_sorting2 = ARTWORKSORTING(entry2);
3003 if (entry1->parent_link || entry2->parent_link)
3004 compare_result = (entry1->parent_link ? -1 : +1);
3005 else if (entry1->sort_priority == entry2->sort_priority)
3007 char *name1 = getStringToLower(entry1->name_sorting);
3008 char *name2 = getStringToLower(entry2->name_sorting);
3010 compare_result = strcmp(name1, name2);
3015 else if (class_sorting1 == class_sorting2)
3016 compare_result = entry1->sort_priority - entry2->sort_priority;
3018 compare_result = class_sorting1 - class_sorting2;
3020 return compare_result;
3023 static void createParentTreeInfoNode(TreeInfo *node_parent)
3027 if (node_parent == NULL)
3030 ti_new = newTreeInfo();
3031 setTreeInfoToDefaults(ti_new, node_parent->type);
3033 ti_new->node_parent = node_parent;
3034 ti_new->parent_link = TRUE;
3036 setString(&ti_new->identifier, node_parent->identifier);
3037 setString(&ti_new->name, ".. (parent directory)");
3038 setString(&ti_new->name_sorting, ti_new->name);
3040 setString(&ti_new->subdir, "..");
3041 setString(&ti_new->fullpath, node_parent->fullpath);
3043 ti_new->sort_priority = node_parent->sort_priority;
3044 ti_new->latest_engine = node_parent->latest_engine;
3046 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3048 pushTreeInfo(&node_parent->node_group, ti_new);
3052 /* -------------------------------------------------------------------------- */
3053 /* functions for handling level and custom artwork info cache */
3054 /* -------------------------------------------------------------------------- */
3056 static void LoadArtworkInfoCache()
3058 InitCacheDirectory();
3060 if (artworkinfo_cache_old == NULL)
3062 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3064 /* try to load artwork info hash from already existing cache file */
3065 artworkinfo_cache_old = loadSetupFileHash(filename);
3067 /* if no artwork info cache file was found, start with empty hash */
3068 if (artworkinfo_cache_old == NULL)
3069 artworkinfo_cache_old = newSetupFileHash();
3074 if (artworkinfo_cache_new == NULL)
3075 artworkinfo_cache_new = newSetupFileHash();
3078 static void SaveArtworkInfoCache()
3080 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3082 InitCacheDirectory();
3084 saveSetupFileHash(artworkinfo_cache_new, filename);
3089 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3091 static char *prefix = NULL;
3093 checked_free(prefix);
3095 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3100 /* (identical to above function, but separate string buffer needed -- nasty) */
3101 static char *getCacheToken(char *prefix, char *suffix)
3103 static char *token = NULL;
3105 checked_free(token);
3107 token = getStringCat2WithSeparator(prefix, suffix, ".");
3112 static char *getFileTimestampString(char *filename)
3115 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3117 struct stat file_status;
3119 if (stat(filename, &file_status) != 0) /* cannot stat file */
3120 return getStringCopy(i_to_a(0));
3122 return getStringCopy(i_to_a(file_status.st_mtime));
3126 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3128 struct stat file_status;
3130 if (timestamp_string == NULL)
3133 if (stat(filename, &file_status) != 0) /* cannot stat file */
3136 return (file_status.st_mtime != atoi(timestamp_string));
3139 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3141 char *identifier = level_node->subdir;
3142 char *type_string = ARTWORK_DIRECTORY(type);
3143 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3144 char *token_main = getCacheToken(token_prefix, "CACHED");
3145 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3146 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3147 TreeInfo *artwork_info = NULL;
3149 if (!use_artworkinfo_cache)
3156 artwork_info = newTreeInfo();
3157 setTreeInfoToDefaults(artwork_info, type);
3159 /* set all structure fields according to the token/value pairs */
3160 ldi = *artwork_info;
3161 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3163 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3164 char *value = getHashEntry(artworkinfo_cache_old, token);
3166 setSetupInfo(artworkinfo_tokens, i, value);
3168 /* check if cache entry for this item is invalid or incomplete */
3172 Error(ERR_WARN, "cache entry '%s' invalid", token);
3179 *artwork_info = ldi;
3184 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3185 LEVELINFO_FILENAME);
3186 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3187 ARTWORKINFO_FILENAME(type));
3189 /* check if corresponding "levelinfo.conf" file has changed */
3190 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3191 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3193 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3196 /* check if corresponding "<artworkinfo>.conf" file has changed */
3197 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3198 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3200 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3205 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
3208 checked_free(filename_levelinfo);
3209 checked_free(filename_artworkinfo);
3212 if (!cached && artwork_info != NULL)
3214 freeTreeInfo(artwork_info);
3219 return artwork_info;
3222 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3223 LevelDirTree *level_node, int type)
3225 char *identifier = level_node->subdir;
3226 char *type_string = ARTWORK_DIRECTORY(type);
3227 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3228 char *token_main = getCacheToken(token_prefix, "CACHED");
3229 boolean set_cache_timestamps = TRUE;
3232 setHashEntry(artworkinfo_cache_new, token_main, "true");
3234 if (set_cache_timestamps)
3236 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3237 LEVELINFO_FILENAME);
3238 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3239 ARTWORKINFO_FILENAME(type));
3240 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3241 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3243 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3244 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3246 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3247 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3249 checked_free(filename_levelinfo);
3250 checked_free(filename_artworkinfo);
3251 checked_free(timestamp_levelinfo);
3252 checked_free(timestamp_artworkinfo);
3255 ldi = *artwork_info;
3256 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3258 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3259 char *value = getSetupValue(artworkinfo_tokens[i].type,
3260 artworkinfo_tokens[i].value);
3262 setHashEntry(artworkinfo_cache_new, token, value);
3267 /* -------------------------------------------------------------------------- */
3268 /* functions for loading level info and custom artwork info */
3269 /* -------------------------------------------------------------------------- */
3271 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3272 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3274 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3275 TreeInfo *node_parent,
3276 char *level_directory,
3277 char *directory_name)
3280 static unsigned int progress_delay = 0;
3281 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3283 char *directory_path = getPath2(level_directory, directory_name);
3284 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3285 SetupFileHash *setup_file_hash;
3286 LevelDirTree *leveldir_new = NULL;
3289 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3290 if (!options.debug && !fileExists(filename))
3292 free(directory_path);
3298 setup_file_hash = loadSetupFileHash(filename);
3300 if (setup_file_hash == NULL)
3302 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3304 free(directory_path);
3310 leveldir_new = newTreeInfo();
3313 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3315 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3317 leveldir_new->subdir = getStringCopy(directory_name);
3319 checkSetupFileHashIdentifier(setup_file_hash, filename,
3320 getCookie("LEVELINFO"));
3322 /* set all structure fields according to the token/value pairs */
3323 ldi = *leveldir_new;
3324 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3325 setSetupInfo(levelinfo_tokens, i,
3326 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3327 *leveldir_new = ldi;
3329 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3330 setString(&leveldir_new->name, leveldir_new->subdir);
3332 if (leveldir_new->identifier == NULL)
3333 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3335 if (leveldir_new->name_sorting == NULL)
3336 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3338 if (node_parent == NULL) /* top level group */
3340 leveldir_new->basepath = getStringCopy(level_directory);
3341 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3343 else /* sub level group */
3345 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3346 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3350 if (leveldir_new->levels < 1)
3351 leveldir_new->levels = 1;
3354 leveldir_new->last_level =
3355 leveldir_new->first_level + leveldir_new->levels - 1;
3357 leveldir_new->in_user_dir =
3358 (!strEqual(leveldir_new->basepath, options.level_directory));
3361 printf("::: '%s' -> %d\n",
3362 leveldir_new->identifier,
3363 leveldir_new->in_user_dir);
3366 /* adjust some settings if user's private level directory was detected */
3367 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3368 leveldir_new->in_user_dir &&
3369 (strEqual(leveldir_new->subdir, getLoginName()) ||
3370 strEqual(leveldir_new->name, getLoginName()) ||
3371 strEqual(leveldir_new->author, getRealName())))
3373 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3374 leveldir_new->readonly = FALSE;
3377 leveldir_new->user_defined =
3378 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3380 leveldir_new->color = LEVELCOLOR(leveldir_new);
3382 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3384 leveldir_new->handicap_level = /* set handicap to default value */
3385 (leveldir_new->user_defined || !leveldir_new->handicap ?
3386 leveldir_new->last_level : leveldir_new->first_level);
3390 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3391 leveldir_new->level_group);
3393 if (leveldir_new->level_group ||
3394 DelayReached(&progress_delay, progress_delay_value))
3395 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3398 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3402 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3404 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3406 /* skip level sets without levels (which are probably artwork base sets) */
3408 freeSetupFileHash(setup_file_hash);
3409 free(directory_path);
3417 pushTreeInfo(node_first, leveldir_new);
3419 freeSetupFileHash(setup_file_hash);
3421 if (leveldir_new->level_group)
3423 /* create node to link back to current level directory */
3424 createParentTreeInfoNode(leveldir_new);
3426 /* recursively step into sub-directory and look for more level series */
3427 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3428 leveldir_new, directory_path);
3431 free(directory_path);
3438 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3439 TreeInfo *node_parent,
3440 char *level_directory)
3443 DirectoryEntry *dir_entry;
3444 boolean valid_entry_found = FALSE;
3447 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3450 if ((dir = openDirectory(level_directory)) == NULL)
3452 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3458 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3461 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3463 char *directory_name = dir_entry->basename;
3464 char *directory_path = getPath2(level_directory, directory_name);
3467 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3470 /* skip entries for current and parent directory */
3471 if (strEqual(directory_name, ".") ||
3472 strEqual(directory_name, ".."))
3474 free(directory_path);
3480 /* find out if directory entry is itself a directory */
3481 if (!dir_entry->is_directory) /* not a directory */
3483 free(directory_path);
3486 Error(ERR_INFO, "* entry '%s' is not a directory ...", directory_name);
3492 /* find out if directory entry is itself a directory */
3493 struct stat file_status;
3494 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3495 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3497 free(directory_path);
3503 free(directory_path);
3505 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3506 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3507 strEqual(directory_name, MUSIC_DIRECTORY))
3510 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3515 closeDirectory(dir);
3517 /* special case: top level directory may directly contain "levelinfo.conf" */
3518 if (node_parent == NULL && !valid_entry_found)
3520 /* check if this directory directly contains a file "levelinfo.conf" */
3521 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3522 level_directory, ".");
3525 if (!valid_entry_found)
3526 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3532 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3533 TreeInfo *node_parent,
3534 char *level_directory)
3537 struct dirent *dir_entry;
3538 boolean valid_entry_found = FALSE;
3541 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3544 if ((dir = opendir(level_directory)) == NULL)
3546 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3552 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3555 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3557 struct stat file_status;
3558 char *directory_name = dir_entry->d_name;
3559 char *directory_path = getPath2(level_directory, directory_name);
3562 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3565 /* skip entries for current and parent directory */
3566 if (strEqual(directory_name, ".") ||
3567 strEqual(directory_name, ".."))
3569 free(directory_path);
3573 /* find out if directory entry is itself a directory */
3574 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3575 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3577 free(directory_path);
3581 free(directory_path);
3583 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3584 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3585 strEqual(directory_name, MUSIC_DIRECTORY))
3588 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3595 /* special case: top level directory may directly contain "levelinfo.conf" */
3596 if (node_parent == NULL && !valid_entry_found)
3598 /* check if this directory directly contains a file "levelinfo.conf" */
3599 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3600 level_directory, ".");
3603 if (!valid_entry_found)
3604 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3609 boolean AdjustGraphicsForEMC()
3611 boolean settings_changed = FALSE;
3613 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3614 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3616 return settings_changed;
3619 void LoadLevelInfo()
3621 InitUserLevelDirectory(getLoginName());
3623 DrawInitText("Loading level series", 120, FC_GREEN);
3625 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3626 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3628 /* after loading all level set information, clone the level directory tree
3629 and remove all level sets without levels (these may still contain artwork
3630 to be offered in the setup menu as "custom artwork", and are therefore
3631 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3632 leveldir_first_all = leveldir_first;
3633 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3635 AdjustGraphicsForEMC();
3637 /* before sorting, the first entries will be from the user directory */
3638 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3640 if (leveldir_first == NULL)
3641 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3643 sortTreeInfo(&leveldir_first);
3646 dumpTreeInfo(leveldir_first, 0);
3652 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3653 TreeInfo *node_parent,
3654 char *base_directory,
3655 char *directory_name, int type)
3657 char *directory_path = getPath2(base_directory, directory_name);
3658 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3659 SetupFileHash *setup_file_hash = NULL;
3660 TreeInfo *artwork_new = NULL;
3663 if (fileExists(filename))
3664 setup_file_hash = loadSetupFileHash(filename);
3666 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3669 DirectoryEntry *dir_entry;
3670 boolean valid_file_found = FALSE;
3672 if ((dir = openDirectory(directory_path)) != NULL)
3674 while ((dir_entry = readDirectory(dir)) != NULL)
3676 char *entry_name = dir_entry->basename;
3678 if (FileIsArtworkType(entry_name, type))
3680 valid_file_found = TRUE;
3686 closeDirectory(dir);
3689 if (!valid_file_found)
3691 if (!strEqual(directory_name, "."))
3692 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3694 free(directory_path);
3701 artwork_new = newTreeInfo();
3704 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3706 setTreeInfoToDefaults(artwork_new, type);
3708 artwork_new->subdir = getStringCopy(directory_name);
3710 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3713 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3716 /* set all structure fields according to the token/value pairs */
3718 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3719 setSetupInfo(levelinfo_tokens, i,
3720 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3723 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3724 setString(&artwork_new->name, artwork_new->subdir);
3726 if (artwork_new->identifier == NULL)
3727 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3729 if (artwork_new->name_sorting == NULL)
3730 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3733 if (node_parent == NULL) /* top level group */
3735 artwork_new->basepath = getStringCopy(base_directory);
3736 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3738 else /* sub level group */
3740 artwork_new->basepath = getStringCopy(node_parent->basepath);
3741 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3744 artwork_new->in_user_dir =
3745 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3747 /* (may use ".sort_priority" from "setup_file_hash" above) */
3748 artwork_new->color = ARTWORKCOLOR(artwork_new);
3750 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3752 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3754 if (strEqual(artwork_new->subdir, "."))
3756 if (artwork_new->user_defined)
3758 setString(&artwork_new->identifier, "private");
3759 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3763 setString(&artwork_new->identifier, "classic");
3764 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3767 /* set to new values after changing ".sort_priority" */
3768 artwork_new->color = ARTWORKCOLOR(artwork_new);
3770 setString(&artwork_new->class_desc,
3771 getLevelClassDescription(artwork_new));
3775 setString(&artwork_new->identifier, artwork_new->subdir);
3778 setString(&artwork_new->name, artwork_new->identifier);
3779 setString(&artwork_new->name_sorting, artwork_new->name);
3783 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3786 pushTreeInfo(node_first, artwork_new);
3788 freeSetupFileHash(setup_file_hash);
3790 free(directory_path);
3798 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3799 TreeInfo *node_parent,
3800 char *base_directory,
3801 char *directory_name, int type)
3803 char *directory_path = getPath2(base_directory, directory_name);
3804 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3805 SetupFileHash *setup_file_hash = NULL;
3806 TreeInfo *artwork_new = NULL;
3809 if (fileExists(filename))
3810 setup_file_hash = loadSetupFileHash(filename);
3812 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3815 struct dirent *dir_entry;
3816 boolean valid_file_found = FALSE;
3818 if ((dir = opendir(directory_path)) != NULL)
3820 while ((dir_entry = readdir(dir)) != NULL)
3822 char *entry_name = dir_entry->d_name;
3824 if (FileIsArtworkType(entry_name, type))
3826 valid_file_found = TRUE;
3834 if (!valid_file_found)
3836 if (!strEqual(directory_name, "."))
3837 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3839 free(directory_path);
3846 artwork_new = newTreeInfo();
3849 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3851 setTreeInfoToDefaults(artwork_new, type);
3853 artwork_new->subdir = getStringCopy(directory_name);
3855 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3858 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3861 /* set all structure fields according to the token/value pairs */
3863 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3864 setSetupInfo(levelinfo_tokens, i,
3865 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3868 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3869 setString(&artwork_new->name, artwork_new->subdir);
3871 if (artwork_new->identifier == NULL)
3872 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3874 if (artwork_new->name_sorting == NULL)
3875 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3878 if (node_parent == NULL) /* top level group */
3880 artwork_new->basepath = getStringCopy(base_directory);
3881 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3883 else /* sub level group */
3885 artwork_new->basepath = getStringCopy(node_parent->basepath);
3886 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3889 artwork_new->in_user_dir =
3890 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3892 /* (may use ".sort_priority" from "setup_file_hash" above) */
3893 artwork_new->color = ARTWORKCOLOR(artwork_new);
3895 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3897 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3899 if (strEqual(artwork_new->subdir, "."))
3901 if (artwork_new->user_defined)
3903 setString(&artwork_new->identifier, "private");
3904 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3908 setString(&artwork_new->identifier, "classic");
3909 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3912 /* set to new values after changing ".sort_priority" */
3913 artwork_new->color = ARTWORKCOLOR(artwork_new);
3915 setString(&artwork_new->class_desc,
3916 getLevelClassDescription(artwork_new));
3920 setString(&artwork_new->identifier, artwork_new->subdir);
3923 setString(&artwork_new->name, artwork_new->identifier);
3924 setString(&artwork_new->name_sorting, artwork_new->name);
3928 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3931 pushTreeInfo(node_first, artwork_new);
3933 freeSetupFileHash(setup_file_hash);
3935 free(directory_path);
3945 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3946 TreeInfo *node_parent,
3947 char *base_directory, int type)
3950 DirectoryEntry *dir_entry;
3951 boolean valid_entry_found = FALSE;
3953 if ((dir = openDirectory(base_directory)) == NULL)
3955 /* display error if directory is main "options.graphics_directory" etc. */
3956 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3957 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3962 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3964 char *directory_name = dir_entry->basename;
3965 char *directory_path = getPath2(base_directory, directory_name);
3967 /* skip directory entries for current and parent directory */
3968 if (strEqual(directory_name, ".") ||
3969 strEqual(directory_name, ".."))
3971 free(directory_path);
3977 /* skip directory entries which are not a directory */
3978 if (!dir_entry->is_directory) /* not a directory */
3980 free(directory_path);
3985 /* skip directory entries which are not a directory or are not accessible */
3986 struct stat file_status;
3987 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3988 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3990 free(directory_path);
3996 free(directory_path);
3998 /* check if this directory contains artwork with or without config file */
3999 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4001 directory_name, type);
4004 closeDirectory(dir);
4006 /* check if this directory directly contains artwork itself */
4007 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4008 base_directory, ".",
4010 if (!valid_entry_found)
4011 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4017 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4018 TreeInfo *node_parent,
4019 char *base_directory, int type)
4022 struct dirent *dir_entry;
4023 boolean valid_entry_found = FALSE;
4025 if ((dir = opendir(base_directory)) == NULL)
4027 /* display error if directory is main "options.graphics_directory" etc. */
4028 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4029 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
4034 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
4036 struct stat file_status;
4037 char *directory_name = dir_entry->d_name;
4038 char *directory_path = getPath2(base_directory, directory_name);
4040 /* skip directory entries for current and parent directory */
4041 if (strEqual(directory_name, ".") ||
4042 strEqual(directory_name, ".."))
4044 free(directory_path);
4048 /* skip directory entries which are not a directory or are not accessible */
4049 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
4050 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
4052 free(directory_path);
4056 free(directory_path);
4058 /* check if this directory contains artwork with or without config file */
4059 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4061 directory_name, type);
4066 /* check if this directory directly contains artwork itself */
4067 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4068 base_directory, ".",
4070 if (!valid_entry_found)
4071 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4077 static TreeInfo *getDummyArtworkInfo(int type)
4079 /* this is only needed when there is completely no artwork available */
4080 TreeInfo *artwork_new = newTreeInfo();
4082 setTreeInfoToDefaults(artwork_new, type);
4084 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4085 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4086 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4088 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4089 setString(&artwork_new->name, UNDEFINED_FILENAME);
4090 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4095 void LoadArtworkInfo()
4097 LoadArtworkInfoCache();
4099 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
4101 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4102 options.graphics_directory,
4103 TREE_TYPE_GRAPHICS_DIR);
4104 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4105 getUserGraphicsDir(),
4106 TREE_TYPE_GRAPHICS_DIR);
4108 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4109 options.sounds_directory,
4110 TREE_TYPE_SOUNDS_DIR);
4111 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4113 TREE_TYPE_SOUNDS_DIR);
4115 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4116 options.music_directory,
4117 TREE_TYPE_MUSIC_DIR);
4118 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4120 TREE_TYPE_MUSIC_DIR);
4122 if (artwork.gfx_first == NULL)
4123 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4124 if (artwork.snd_first == NULL)
4125 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4126 if (artwork.mus_first == NULL)
4127 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4129 /* before sorting, the first entries will be from the user directory */
4130 artwork.gfx_current =
4131 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4132 if (artwork.gfx_current == NULL)
4133 artwork.gfx_current =
4134 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4135 if (artwork.gfx_current == NULL)
4136 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4138 artwork.snd_current =
4139 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4140 if (artwork.snd_current == NULL)
4141 artwork.snd_current =
4142 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4143 if (artwork.snd_current == NULL)
4144 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4146 artwork.mus_current =
4147 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4148 if (artwork.mus_current == NULL)
4149 artwork.mus_current =
4150 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4151 if (artwork.mus_current == NULL)
4152 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4154 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4155 artwork.snd_current_identifier = artwork.snd_current->identifier;
4156 artwork.mus_current_identifier = artwork.mus_current->identifier;
4159 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
4160 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
4161 printf("music set == %s\n\n", artwork.mus_current_identifier);
4164 sortTreeInfo(&artwork.gfx_first);
4165 sortTreeInfo(&artwork.snd_first);
4166 sortTreeInfo(&artwork.mus_first);
4169 dumpTreeInfo(artwork.gfx_first, 0);
4170 dumpTreeInfo(artwork.snd_first, 0);
4171 dumpTreeInfo(artwork.mus_first, 0);
4175 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
4176 LevelDirTree *level_node)
4179 static unsigned int progress_delay = 0;
4180 unsigned int progress_delay_value = 100; /* (in milliseconds) */
4182 int type = (*artwork_node)->type;
4184 /* recursively check all level directories for artwork sub-directories */
4188 /* check all tree entries for artwork, but skip parent link entries */
4189 if (!level_node->parent_link)
4191 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4192 boolean cached = (artwork_new != NULL);
4196 pushTreeInfo(artwork_node, artwork_new);
4200 TreeInfo *topnode_last = *artwork_node;
4201 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4202 ARTWORK_DIRECTORY(type));
4204 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4206 if (topnode_last != *artwork_node) /* check for newly added node */
4208 artwork_new = *artwork_node;
4210 setString(&artwork_new->identifier, level_node->subdir);
4211 setString(&artwork_new->name, level_node->name);
4212 setString(&artwork_new->name_sorting, level_node->name_sorting);
4214 artwork_new->sort_priority = level_node->sort_priority;
4215 artwork_new->color = LEVELCOLOR(artwork_new);
4221 /* insert artwork info (from old cache or filesystem) into new cache */
4222 if (artwork_new != NULL)
4223 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4227 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
4228 level_node->level_group);
4230 if (level_node->level_group ||
4231 DelayReached(&progress_delay, progress_delay_value))
4232 DrawInitText(level_node->name, 150, FC_YELLOW);
4235 if (level_node->node_group != NULL)
4236 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
4238 level_node = level_node->next;
4242 void LoadLevelArtworkInfo()
4244 print_timestamp_init("LoadLevelArtworkInfo");
4246 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4248 print_timestamp_time("DrawTimeText");
4250 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
4251 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4252 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
4253 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4254 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
4255 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4257 SaveArtworkInfoCache();
4259 print_timestamp_time("SaveArtworkInfoCache");
4261 /* needed for reloading level artwork not known at ealier stage */
4263 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
4265 artwork.gfx_current =
4266 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4267 if (artwork.gfx_current == NULL)
4268 artwork.gfx_current =
4269 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4270 if (artwork.gfx_current == NULL)
4271 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4274 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
4276 artwork.snd_current =
4277 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4278 if (artwork.snd_current == NULL)
4279 artwork.snd_current =
4280 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4281 if (artwork.snd_current == NULL)
4282 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4285 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
4287 artwork.mus_current =
4288 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4289 if (artwork.mus_current == NULL)
4290 artwork.mus_current =
4291 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4292 if (artwork.mus_current == NULL)
4293 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4296 print_timestamp_time("getTreeInfoFromIdentifier");
4298 sortTreeInfo(&artwork.gfx_first);
4299 sortTreeInfo(&artwork.snd_first);
4300 sortTreeInfo(&artwork.mus_first);
4302 print_timestamp_time("sortTreeInfo");
4305 dumpTreeInfo(artwork.gfx_first, 0);
4306 dumpTreeInfo(artwork.snd_first, 0);
4307 dumpTreeInfo(artwork.mus_first, 0);
4310 print_timestamp_done("LoadLevelArtworkInfo");
4313 static void SaveUserLevelInfo()
4315 LevelDirTree *level_info;
4320 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
4322 if (!(file = fopen(filename, MODE_WRITE)))
4324 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4329 level_info = newTreeInfo();
4331 /* always start with reliable default values */
4332 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4334 setString(&level_info->name, getLoginName());
4335 setString(&level_info->author, getRealName());
4336 level_info->levels = 100;
4337 level_info->first_level = 1;
4339 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4341 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4342 getCookie("LEVELINFO")));
4345 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4347 if (i == LEVELINFO_TOKEN_NAME ||
4348 i == LEVELINFO_TOKEN_AUTHOR ||
4349 i == LEVELINFO_TOKEN_LEVELS ||
4350 i == LEVELINFO_TOKEN_FIRST_LEVEL)
4351 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4353 /* just to make things nicer :) */
4354 if (i == LEVELINFO_TOKEN_AUTHOR)
4355 fprintf(file, "\n");
4358 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4362 SetFilePermissions(filename, PERMS_PRIVATE);
4364 freeTreeInfo(level_info);
4368 char *getSetupValue(int type, void *value)
4370 static char value_string[MAX_LINE_LEN];
4378 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4382 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4386 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4387 *(int *)value == FALSE ? "off" : "on"));
4391 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4394 case TYPE_YES_NO_AUTO:
4395 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4396 *(int *)value == FALSE ? "no" : "yes"));
4400 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4404 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4408 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4412 sprintf(value_string, "%d", *(int *)value);
4416 if (*(char **)value == NULL)
4419 strcpy(value_string, *(char **)value);
4423 value_string[0] = '\0';
4427 if (type & TYPE_GHOSTED)
4428 strcpy(value_string, "n/a");
4430 return value_string;
4433 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4437 static char token_string[MAX_LINE_LEN];
4438 int token_type = token_info[token_nr].type;
4439 void *setup_value = token_info[token_nr].value;
4440 char *token_text = token_info[token_nr].text;
4441 char *value_string = getSetupValue(token_type, setup_value);
4443 /* build complete token string */
4444 sprintf(token_string, "%s%s", prefix, token_text);
4446 /* build setup entry line */
4447 line = getFormattedSetupEntry(token_string, value_string);
4449 if (token_type == TYPE_KEY_X11)
4451 Key key = *(Key *)setup_value;
4452 char *keyname = getKeyNameFromKey(key);
4454 /* add comment, if useful */
4455 if (!strEqual(keyname, "(undefined)") &&
4456 !strEqual(keyname, "(unknown)"))
4458 /* add at least one whitespace */
4460 for (i = strlen(line); i < token_comment_position; i++)
4464 strcat(line, keyname);
4471 void LoadLevelSetup_LastSeries()
4473 /* ----------------------------------------------------------------------- */
4474 /* ~/.<program>/levelsetup.conf */
4475 /* ----------------------------------------------------------------------- */
4477 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4478 SetupFileHash *level_setup_hash = NULL;
4480 /* always start with reliable default values */
4481 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4483 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
4484 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4486 if (leveldir_current == NULL)
4487 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4490 if ((level_setup_hash = loadSetupFileHash(filename)))
4492 char *last_level_series =
4493 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4495 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4497 if (leveldir_current == NULL)
4498 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4500 checkSetupFileHashIdentifier(level_setup_hash, filename,
4501 getCookie("LEVELSETUP"));
4503 freeSetupFileHash(level_setup_hash);
4506 Error(ERR_WARN, "using default setup values");
4511 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4513 /* ----------------------------------------------------------------------- */
4514 /* ~/.<program>/levelsetup.conf */
4515 /* ----------------------------------------------------------------------- */
4517 // check if the current level directory structure is available at this point
4518 if (leveldir_current == NULL)
4521 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4522 char *level_subdir = leveldir_current->subdir;
4525 InitUserDataDirectory();
4527 if (!(file = fopen(filename, MODE_WRITE)))
4529 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4536 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4537 getCookie("LEVELSETUP")));
4539 if (deactivate_last_level_series)
4540 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4542 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4547 SetFilePermissions(filename, PERMS_PRIVATE);
4552 void SaveLevelSetup_LastSeries()
4554 SaveLevelSetup_LastSeries_Ext(FALSE);
4557 void SaveLevelSetup_LastSeries_Deactivate()
4559 SaveLevelSetup_LastSeries_Ext(TRUE);
4564 static void checkSeriesInfo()
4566 static char *level_directory = NULL;
4569 DirectoryEntry *dir_entry;
4572 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4574 level_directory = getPath2((leveldir_current->in_user_dir ?
4575 getUserLevelDir(NULL) :
4576 options.level_directory),
4577 leveldir_current->fullpath);
4579 if ((dir = openDirectory(level_directory)) == NULL)
4581 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4587 while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */
4589 if (strlen(dir_entry->basename) > 4 &&
4590 dir_entry->basename[3] == '.' &&
4591 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4593 char levelnum_str[4];
4596 strncpy(levelnum_str, dir_entry->basename, 3);
4597 levelnum_str[3] = '\0';
4599 levelnum_value = atoi(levelnum_str);
4601 if (levelnum_value < leveldir_current->first_level)
4603 Error(ERR_WARN, "additional level %d found", levelnum_value);
4604 leveldir_current->first_level = levelnum_value;
4606 else if (levelnum_value > leveldir_current->last_level)
4608 Error(ERR_WARN, "additional level %d found", levelnum_value);
4609 leveldir_current->last_level = levelnum_value;
4615 closeDirectory(dir);
4620 static void checkSeriesInfo()
4622 static char *level_directory = NULL;
4625 struct dirent *dir_entry;
4628 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4630 level_directory = getPath2((leveldir_current->in_user_dir ?
4631 getUserLevelDir(NULL) :
4632 options.level_directory),
4633 leveldir_current->fullpath);
4635 if ((dir = opendir(level_directory)) == NULL)
4637 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4643 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4645 if (strlen(dir_entry->d_name) > 4 &&
4646 dir_entry->d_name[3] == '.' &&
4647 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4649 char levelnum_str[4];
4652 strncpy(levelnum_str, dir_entry->d_name, 3);
4653 levelnum_str[3] = '\0';
4655 levelnum_value = atoi(levelnum_str);
4657 if (levelnum_value < leveldir_current->first_level)
4659 Error(ERR_WARN, "additional level %d found", levelnum_value);
4660 leveldir_current->first_level = levelnum_value;
4662 else if (levelnum_value > leveldir_current->last_level)
4664 Error(ERR_WARN, "additional level %d found", levelnum_value);
4665 leveldir_current->last_level = levelnum_value;
4676 void LoadLevelSetup_SeriesInfo()
4679 SetupFileHash *level_setup_hash = NULL;
4680 char *level_subdir = leveldir_current->subdir;
4683 /* always start with reliable default values */
4684 level_nr = leveldir_current->first_level;
4686 for (i = 0; i < MAX_LEVELS; i++)
4688 LevelStats_setPlayed(i, 0);
4689 LevelStats_setSolved(i, 0);
4692 checkSeriesInfo(leveldir_current);
4694 /* ----------------------------------------------------------------------- */
4695 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4696 /* ----------------------------------------------------------------------- */
4698 level_subdir = leveldir_current->subdir;
4700 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4702 if ((level_setup_hash = loadSetupFileHash(filename)))
4706 /* get last played level in this level set */
4708 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4712 level_nr = atoi(token_value);
4714 if (level_nr < leveldir_current->first_level)
4715 level_nr = leveldir_current->first_level;
4716 if (level_nr > leveldir_current->last_level)
4717 level_nr = leveldir_current->last_level;
4720 /* get handicap level in this level set */
4722 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4726 int level_nr = atoi(token_value);
4728 if (level_nr < leveldir_current->first_level)
4729 level_nr = leveldir_current->first_level;
4730 if (level_nr > leveldir_current->last_level + 1)
4731 level_nr = leveldir_current->last_level;
4733 if (leveldir_current->user_defined || !leveldir_current->handicap)
4734 level_nr = leveldir_current->last_level;
4736 leveldir_current->handicap_level = level_nr;
4739 /* get number of played and solved levels in this level set */
4741 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4743 char *token = HASH_ITERATION_TOKEN(itr);
4744 char *value = HASH_ITERATION_VALUE(itr);
4746 if (strlen(token) == 3 &&
4747 token[0] >= '0' && token[0] <= '9' &&
4748 token[1] >= '0' && token[1] <= '9' &&
4749 token[2] >= '0' && token[2] <= '9')
4751 int level_nr = atoi(token);
4754 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4756 value = strchr(value, ' ');
4759 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4762 END_HASH_ITERATION(hash, itr)
4764 checkSetupFileHashIdentifier(level_setup_hash, filename,
4765 getCookie("LEVELSETUP"));
4767 freeSetupFileHash(level_setup_hash);
4770 Error(ERR_WARN, "using default setup values");
4775 void SaveLevelSetup_SeriesInfo()
4778 char *level_subdir = leveldir_current->subdir;
4779 char *level_nr_str = int2str(level_nr, 0);
4780 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4784 /* ----------------------------------------------------------------------- */
4785 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4786 /* ----------------------------------------------------------------------- */
4788 InitLevelSetupDirectory(level_subdir);
4790 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4792 if (!(file = fopen(filename, MODE_WRITE)))
4794 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4799 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4800 getCookie("LEVELSETUP")));
4801 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4803 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4804 handicap_level_str));
4806 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4809 if (LevelStats_getPlayed(i) > 0 ||
4810 LevelStats_getSolved(i) > 0)
4815 sprintf(token, "%03d", i);
4816 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4818 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4824 SetFilePermissions(filename, PERMS_PRIVATE);
4829 int LevelStats_getPlayed(int nr)
4831 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4834 int LevelStats_getSolved(int nr)
4836 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4839 void LevelStats_setPlayed(int nr, int value)
4841 if (nr >= 0 && nr < MAX_LEVELS)
4842 level_stats[nr].played = value;
4845 void LevelStats_setSolved(int nr, int value)
4847 if (nr >= 0 && nr < MAX_LEVELS)
4848 level_stats[nr].solved = value;
4851 void LevelStats_incPlayed(int nr)
4853 if (nr >= 0 && nr < MAX_LEVELS)
4854 level_stats[nr].played++;
4857 void LevelStats_incSolved(int nr)
4859 if (nr >= 0 && nr < MAX_LEVELS)
4860 level_stats[nr].solved++;