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);
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 (fileExists(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 (fileExists(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 (fileExists(directory))
913 /* 4th try: look for default artwork in new default artwork directory */
914 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
915 if (fileExists(directory))
920 /* 5th try: look for default artwork in old default artwork directory */
921 directory = getStringCopy(options.music_directory);
922 if (fileExists(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 (!fileExists(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 (user_game_data_dir == NULL)
1379 user_game_data_dir = getPath2(getPersonalDataDir(),
1380 program.userdata_subdir);
1382 return user_game_data_dir;
1385 void updateUserGameDataDir()
1387 #if defined(PLATFORM_MACOSX)
1388 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1389 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1391 /* convert old Unix style game data directory to Mac OS X style, if needed */
1392 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1394 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1396 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1397 userdata_dir_old, userdata_dir_new);
1399 /* continue using Unix style data directory -- this should not happen */
1400 program.userdata_path = getPath2(getPersonalDataDir(),
1401 program.userdata_subdir_unix);
1405 free(userdata_dir_old);
1411 return getUserGameDataDir();
1414 static mode_t posix_umask(mode_t mask)
1416 #if defined(PLATFORM_UNIX)
1423 static int posix_mkdir(const char *pathname, mode_t mode)
1425 #if defined(PLATFORM_WIN32)
1426 return mkdir(pathname);
1428 return mkdir(pathname, mode);
1432 static boolean posix_process_running_setgid()
1434 #if defined(PLATFORM_UNIX)
1435 return (getgid() != getegid());
1441 void createDirectory(char *dir, char *text, int permission_class)
1443 /* leave "other" permissions in umask untouched, but ensure group parts
1444 of USERDATA_DIR_MODE are not masked */
1445 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1446 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1447 mode_t last_umask = posix_umask(0);
1448 mode_t group_umask = ~(dir_mode & S_IRWXG);
1449 int running_setgid = posix_process_running_setgid();
1451 /* if we're setgid, protect files against "other" */
1452 /* else keep umask(0) to make the dir world-writable */
1455 posix_umask(last_umask & group_umask);
1457 dir_mode |= MODE_W_ALL;
1459 if (!fileExists(dir))
1460 if (posix_mkdir(dir, dir_mode) != 0)
1461 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1462 text, dir, strerror(errno));
1464 if (permission_class == PERMS_PUBLIC && !running_setgid)
1465 chmod(dir, dir_mode);
1467 posix_umask(last_umask); /* restore previous umask */
1470 void InitUserDataDirectory()
1472 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1475 void SetFilePermissions(char *filename, int permission_class)
1477 int running_setgid = posix_process_running_setgid();
1478 int perms = (permission_class == PERMS_PRIVATE ?
1479 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1481 if (permission_class == PERMS_PUBLIC && !running_setgid)
1482 perms |= MODE_W_ALL;
1484 chmod(filename, perms);
1487 char *getCookie(char *file_type)
1489 static char cookie[MAX_COOKIE_LEN + 1];
1491 if (strlen(program.cookie_prefix) + 1 +
1492 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1493 return "[COOKIE ERROR]"; /* should never happen */
1495 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1496 program.cookie_prefix, file_type,
1497 program.version_major, program.version_minor);
1502 int getFileVersionFromCookieString(const char *cookie)
1504 const char *ptr_cookie1, *ptr_cookie2;
1505 const char *pattern1 = "_FILE_VERSION_";
1506 const char *pattern2 = "?.?";
1507 const int len_cookie = strlen(cookie);
1508 const int len_pattern1 = strlen(pattern1);
1509 const int len_pattern2 = strlen(pattern2);
1510 const int len_pattern = len_pattern1 + len_pattern2;
1511 int version_major, version_minor;
1513 if (len_cookie <= len_pattern)
1516 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1517 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1519 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1522 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1523 ptr_cookie2[1] != '.' ||
1524 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1527 version_major = ptr_cookie2[0] - '0';
1528 version_minor = ptr_cookie2[2] - '0';
1530 return VERSION_IDENT(version_major, version_minor, 0, 0);
1533 boolean checkCookieString(const char *cookie, const char *template)
1535 const char *pattern = "_FILE_VERSION_?.?";
1536 const int len_cookie = strlen(cookie);
1537 const int len_template = strlen(template);
1538 const int len_pattern = strlen(pattern);
1540 if (len_cookie != len_template)
1543 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1549 /* ------------------------------------------------------------------------- */
1550 /* setup file list and hash handling functions */
1551 /* ------------------------------------------------------------------------- */
1553 char *getFormattedSetupEntry(char *token, char *value)
1556 static char entry[MAX_LINE_LEN];
1558 /* if value is an empty string, just return token without value */
1562 /* start with the token and some spaces to format output line */
1563 sprintf(entry, "%s:", token);
1564 for (i = strlen(entry); i < token_value_position; i++)
1567 /* continue with the token's value */
1568 strcat(entry, value);
1573 SetupFileList *newSetupFileList(char *token, char *value)
1575 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1577 new->token = getStringCopy(token);
1578 new->value = getStringCopy(value);
1585 void freeSetupFileList(SetupFileList *list)
1590 checked_free(list->token);
1591 checked_free(list->value);
1594 freeSetupFileList(list->next);
1599 char *getListEntry(SetupFileList *list, char *token)
1604 if (strEqual(list->token, token))
1607 return getListEntry(list->next, token);
1610 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1615 if (strEqual(list->token, token))
1617 checked_free(list->value);
1619 list->value = getStringCopy(value);
1623 else if (list->next == NULL)
1624 return (list->next = newSetupFileList(token, value));
1626 return setListEntry(list->next, token, value);
1629 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1634 if (list->next == NULL)
1635 return (list->next = newSetupFileList(token, value));
1637 return addListEntry(list->next, token, value);
1642 static void printSetupFileList(SetupFileList *list)
1647 printf("token: '%s'\n", list->token);
1648 printf("value: '%s'\n", list->value);
1650 printSetupFileList(list->next);
1656 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1657 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1658 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1659 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1661 #define insert_hash_entry hashtable_insert
1662 #define search_hash_entry hashtable_search
1663 #define change_hash_entry hashtable_change
1664 #define remove_hash_entry hashtable_remove
1667 unsigned int get_hash_from_key(void *key)
1672 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1673 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1674 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1675 it works better than many other constants, prime or not) has never been
1676 adequately explained.
1678 If you just want to have a good hash function, and cannot wait, djb2
1679 is one of the best string hash functions i know. It has excellent
1680 distribution and speed on many different sets of keys and table sizes.
1681 You are not likely to do better with one of the "well known" functions
1682 such as PJW, K&R, etc.
1684 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1687 char *str = (char *)key;
1688 unsigned int hash = 5381;
1691 while ((c = *str++))
1692 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1697 static int keys_are_equal(void *key1, void *key2)
1699 return (strEqual((char *)key1, (char *)key2));
1702 SetupFileHash *newSetupFileHash()
1704 SetupFileHash *new_hash =
1705 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1707 if (new_hash == NULL)
1708 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1713 void freeSetupFileHash(SetupFileHash *hash)
1718 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1721 char *getHashEntry(SetupFileHash *hash, char *token)
1726 return search_hash_entry(hash, token);
1729 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1736 value_copy = getStringCopy(value);
1738 /* change value; if it does not exist, insert it as new */
1739 if (!change_hash_entry(hash, token, value_copy))
1740 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1741 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1744 char *removeHashEntry(SetupFileHash *hash, char *token)
1749 return remove_hash_entry(hash, token);
1753 static void printSetupFileHash(SetupFileHash *hash)
1755 BEGIN_HASH_ITERATION(hash, itr)
1757 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1758 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1760 END_HASH_ITERATION(hash, itr)
1764 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1765 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1766 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1768 static boolean token_value_separator_found = FALSE;
1769 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1770 static boolean token_value_separator_warning = FALSE;
1772 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1773 static boolean token_already_exists_warning = FALSE;
1776 static boolean getTokenValueFromSetupLineExt(char *line,
1777 char **token_ptr, char **value_ptr,
1778 char *filename, char *line_raw,
1780 boolean separator_required)
1782 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1783 char *token, *value, *line_ptr;
1785 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1786 if (line_raw == NULL)
1788 strncpy(line_copy, line, MAX_LINE_LEN);
1789 line_copy[MAX_LINE_LEN] = '\0';
1792 strcpy(line_raw_copy, line_copy);
1793 line_raw = line_raw_copy;
1796 /* cut trailing comment from input line */
1797 for (line_ptr = line; *line_ptr; line_ptr++)
1799 if (*line_ptr == '#')
1806 /* cut trailing whitespaces from input line */
1807 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1808 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1811 /* ignore empty lines */
1815 /* cut leading whitespaces from token */
1816 for (token = line; *token; token++)
1817 if (*token != ' ' && *token != '\t')
1820 /* start with empty value as reliable default */
1823 token_value_separator_found = FALSE;
1825 /* find end of token to determine start of value */
1826 for (line_ptr = token; *line_ptr; line_ptr++)
1829 /* first look for an explicit token/value separator, like ':' or '=' */
1830 if (*line_ptr == ':' || *line_ptr == '=')
1832 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1835 *line_ptr = '\0'; /* terminate token string */
1836 value = line_ptr + 1; /* set beginning of value */
1838 token_value_separator_found = TRUE;
1844 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1845 /* fallback: if no token/value separator found, also allow whitespaces */
1846 if (!token_value_separator_found && !separator_required)
1848 for (line_ptr = token; *line_ptr; line_ptr++)
1850 if (*line_ptr == ' ' || *line_ptr == '\t')
1852 *line_ptr = '\0'; /* terminate token string */
1853 value = line_ptr + 1; /* set beginning of value */
1855 token_value_separator_found = TRUE;
1861 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1862 if (token_value_separator_found)
1864 if (!token_value_separator_warning)
1866 Error(ERR_INFO_LINE, "-");
1868 if (filename != NULL)
1870 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1871 Error(ERR_INFO, "- config file: '%s'", filename);
1875 Error(ERR_WARN, "missing token/value separator(s):");
1878 token_value_separator_warning = TRUE;
1881 if (filename != NULL)
1882 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1884 Error(ERR_INFO, "- line: '%s'", line_raw);
1890 /* cut trailing whitespaces from token */
1891 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1892 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1895 /* cut leading whitespaces from value */
1896 for (; *value; value++)
1897 if (*value != ' ' && *value != '\t')
1902 value = "true"; /* treat tokens without value as "true" */
1911 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1913 /* while the internal (old) interface does not require a token/value
1914 separator (for downwards compatibility with existing files which
1915 don't use them), it is mandatory for the external (new) interface */
1917 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1923 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1924 boolean top_recursion_level, boolean is_hash)
1926 static SetupFileHash *include_filename_hash = NULL;
1927 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1928 char *token, *value, *line_ptr;
1929 void *insert_ptr = NULL;
1930 boolean read_continued_line = FALSE;
1932 int line_nr = 0, token_count = 0, include_count = 0;
1934 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1935 token_value_separator_warning = FALSE;
1938 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1939 token_already_exists_warning = FALSE;
1943 Error(ERR_INFO, "===== opening file: '%s'", filename);
1946 if (!(file = openFile(filename, MODE_READ)))
1948 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1954 Error(ERR_INFO, "===== reading file: '%s'", filename);
1957 /* use "insert pointer" to store list end for constant insertion complexity */
1959 insert_ptr = setup_file_data;
1961 /* on top invocation, create hash to mark included files (to prevent loops) */
1962 if (top_recursion_level)
1963 include_filename_hash = newSetupFileHash();
1965 /* mark this file as already included (to prevent including it again) */
1966 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1968 while (!checkEndOfFile(file))
1970 /* read next line of input file */
1971 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1975 Error(ERR_INFO, "got line: '%s'", line);
1978 /* check if line was completely read and is terminated by line break */
1979 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1982 /* cut trailing line break (this can be newline and/or carriage return) */
1983 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1984 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1987 /* copy raw input line for later use (mainly debugging output) */
1988 strcpy(line_raw, line);
1990 if (read_continued_line)
1993 /* !!! ??? WHY ??? !!! */
1994 /* cut leading whitespaces from input line */
1995 for (line_ptr = line; *line_ptr; line_ptr++)
1996 if (*line_ptr != ' ' && *line_ptr != '\t')
2000 /* append new line to existing line, if there is enough space */
2001 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2002 strcat(previous_line, line_ptr);
2004 strcpy(line, previous_line); /* copy storage buffer to line */
2006 read_continued_line = FALSE;
2009 /* if the last character is '\', continue at next line */
2010 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2012 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2013 strcpy(previous_line, line); /* copy line to storage buffer */
2015 read_continued_line = TRUE;
2020 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2021 line_raw, line_nr, FALSE))
2026 if (strEqual(token, "include"))
2028 if (getHashEntry(include_filename_hash, value) == NULL)
2030 char *basepath = getBasePath(filename);
2031 char *basename = getBaseName(value);
2032 char *filename_include = getPath2(basepath, basename);
2035 Error(ERR_INFO, "[including file '%s']", filename_include);
2038 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2042 free(filename_include);
2048 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2055 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2057 getHashEntry((SetupFileHash *)setup_file_data, token);
2059 if (old_value != NULL)
2061 if (!token_already_exists_warning)
2063 Error(ERR_INFO_LINE, "-");
2064 Error(ERR_WARN, "duplicate token(s) found in config file:");
2065 Error(ERR_INFO, "- config file: '%s'", filename);
2067 token_already_exists_warning = TRUE;
2070 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2071 Error(ERR_INFO, " old value: '%s'", old_value);
2072 Error(ERR_INFO, " new value: '%s'", value);
2076 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2080 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2090 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2091 if (token_value_separator_warning)
2092 Error(ERR_INFO_LINE, "-");
2095 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2096 if (token_already_exists_warning)
2097 Error(ERR_INFO_LINE, "-");
2100 if (token_count == 0 && include_count == 0)
2101 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2103 if (top_recursion_level)
2104 freeSetupFileHash(include_filename_hash);
2111 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2112 boolean top_recursion_level, boolean is_hash)
2114 static SetupFileHash *include_filename_hash = NULL;
2115 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2116 char *token, *value, *line_ptr;
2117 void *insert_ptr = NULL;
2118 boolean read_continued_line = FALSE;
2120 int line_nr = 0, token_count = 0, include_count = 0;
2122 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2123 token_value_separator_warning = FALSE;
2126 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2127 token_already_exists_warning = FALSE;
2130 if (!(file = fopen(filename, MODE_READ)))
2132 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2137 /* use "insert pointer" to store list end for constant insertion complexity */
2139 insert_ptr = setup_file_data;
2141 /* on top invocation, create hash to mark included files (to prevent loops) */
2142 if (top_recursion_level)
2143 include_filename_hash = newSetupFileHash();
2145 /* mark this file as already included (to prevent including it again) */
2146 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2150 /* read next line of input file */
2151 if (!fgets(line, MAX_LINE_LEN, file))
2154 /* check if line was completely read and is terminated by line break */
2155 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2158 /* cut trailing line break (this can be newline and/or carriage return) */
2159 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2160 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2163 /* copy raw input line for later use (mainly debugging output) */
2164 strcpy(line_raw, line);
2166 if (read_continued_line)
2169 /* !!! ??? WHY ??? !!! */
2170 /* cut leading whitespaces from input line */
2171 for (line_ptr = line; *line_ptr; line_ptr++)
2172 if (*line_ptr != ' ' && *line_ptr != '\t')
2176 /* append new line to existing line, if there is enough space */
2177 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2178 strcat(previous_line, line_ptr);
2180 strcpy(line, previous_line); /* copy storage buffer to line */
2182 read_continued_line = FALSE;
2185 /* if the last character is '\', continue at next line */
2186 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2188 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2189 strcpy(previous_line, line); /* copy line to storage buffer */
2191 read_continued_line = TRUE;
2196 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2197 line_raw, line_nr, FALSE))
2202 if (strEqual(token, "include"))
2204 if (getHashEntry(include_filename_hash, value) == NULL)
2206 char *basepath = getBasePath(filename);
2207 char *basename = getBaseName(value);
2208 char *filename_include = getPath2(basepath, basename);
2211 Error(ERR_INFO, "[including file '%s']", filename_include);
2214 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2218 free(filename_include);
2224 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2231 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2233 getHashEntry((SetupFileHash *)setup_file_data, token);
2235 if (old_value != NULL)
2237 if (!token_already_exists_warning)
2239 Error(ERR_INFO_LINE, "-");
2240 Error(ERR_WARN, "duplicate token(s) found in config file:");
2241 Error(ERR_INFO, "- config file: '%s'", filename);
2243 token_already_exists_warning = TRUE;
2246 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2247 Error(ERR_INFO, " old value: '%s'", old_value);
2248 Error(ERR_INFO, " new value: '%s'", value);
2252 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2256 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2266 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2267 if (token_value_separator_warning)
2268 Error(ERR_INFO_LINE, "-");
2271 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2272 if (token_already_exists_warning)
2273 Error(ERR_INFO_LINE, "-");
2276 if (token_count == 0 && include_count == 0)
2277 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2279 if (top_recursion_level)
2280 freeSetupFileHash(include_filename_hash);
2289 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2290 boolean top_recursion_level, boolean is_hash)
2292 static SetupFileHash *include_filename_hash = NULL;
2293 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2294 char *token, *value, *line_ptr;
2295 void *insert_ptr = NULL;
2296 boolean read_continued_line = FALSE;
2299 int token_count = 0;
2301 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2302 token_value_separator_warning = FALSE;
2305 if (!(file = fopen(filename, MODE_READ)))
2307 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2312 /* use "insert pointer" to store list end for constant insertion complexity */
2314 insert_ptr = setup_file_data;
2316 /* on top invocation, create hash to mark included files (to prevent loops) */
2317 if (top_recursion_level)
2318 include_filename_hash = newSetupFileHash();
2320 /* mark this file as already included (to prevent including it again) */
2321 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2325 /* read next line of input file */
2326 if (!fgets(line, MAX_LINE_LEN, file))
2329 /* check if line was completely read and is terminated by line break */
2330 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2333 /* cut trailing line break (this can be newline and/or carriage return) */
2334 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2335 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2338 /* copy raw input line for later use (mainly debugging output) */
2339 strcpy(line_raw, line);
2341 if (read_continued_line)
2343 /* cut leading whitespaces from input line */
2344 for (line_ptr = line; *line_ptr; line_ptr++)
2345 if (*line_ptr != ' ' && *line_ptr != '\t')
2348 /* append new line to existing line, if there is enough space */
2349 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2350 strcat(previous_line, line_ptr);
2352 strcpy(line, previous_line); /* copy storage buffer to line */
2354 read_continued_line = FALSE;
2357 /* if the last character is '\', continue at next line */
2358 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2360 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2361 strcpy(previous_line, line); /* copy line to storage buffer */
2363 read_continued_line = TRUE;
2368 /* cut trailing comment from input line */
2369 for (line_ptr = line; *line_ptr; line_ptr++)
2371 if (*line_ptr == '#')
2378 /* cut trailing whitespaces from input line */
2379 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2380 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2383 /* ignore empty lines */
2387 /* cut leading whitespaces from token */
2388 for (token = line; *token; token++)
2389 if (*token != ' ' && *token != '\t')
2392 /* start with empty value as reliable default */
2395 token_value_separator_found = FALSE;
2397 /* find end of token to determine start of value */
2398 for (line_ptr = token; *line_ptr; line_ptr++)
2401 /* first look for an explicit token/value separator, like ':' or '=' */
2402 if (*line_ptr == ':' || *line_ptr == '=')
2404 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2407 *line_ptr = '\0'; /* terminate token string */
2408 value = line_ptr + 1; /* set beginning of value */
2410 token_value_separator_found = TRUE;
2416 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2417 /* fallback: if no token/value separator found, also allow whitespaces */
2418 if (!token_value_separator_found)
2420 for (line_ptr = token; *line_ptr; line_ptr++)
2422 if (*line_ptr == ' ' || *line_ptr == '\t')
2424 *line_ptr = '\0'; /* terminate token string */
2425 value = line_ptr + 1; /* set beginning of value */
2427 token_value_separator_found = TRUE;
2433 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2434 if (token_value_separator_found)
2436 if (!token_value_separator_warning)
2438 Error(ERR_INFO_LINE, "-");
2439 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2440 Error(ERR_INFO, "- config file: '%s'", filename);
2442 token_value_separator_warning = TRUE;
2445 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2451 /* cut trailing whitespaces from token */
2452 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2453 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2456 /* cut leading whitespaces from value */
2457 for (; *value; value++)
2458 if (*value != ' ' && *value != '\t')
2463 value = "true"; /* treat tokens without value as "true" */
2468 if (strEqual(token, "include"))
2470 if (getHashEntry(include_filename_hash, value) == NULL)
2472 char *basepath = getBasePath(filename);
2473 char *basename = getBaseName(value);
2474 char *filename_include = getPath2(basepath, basename);
2477 Error(ERR_INFO, "[including file '%s']", filename_include);
2480 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2484 free(filename_include);
2488 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2494 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2496 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2505 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2506 if (token_value_separator_warning)
2507 Error(ERR_INFO_LINE, "-");
2510 if (token_count == 0)
2511 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2513 if (top_recursion_level)
2514 freeSetupFileHash(include_filename_hash);
2520 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2524 if (!(file = fopen(filename, MODE_WRITE)))
2526 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2531 BEGIN_HASH_ITERATION(hash, itr)
2533 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2534 HASH_ITERATION_VALUE(itr)));
2536 END_HASH_ITERATION(hash, itr)
2541 SetupFileList *loadSetupFileList(char *filename)
2543 SetupFileList *setup_file_list = newSetupFileList("", "");
2544 SetupFileList *first_valid_list_entry;
2546 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2548 freeSetupFileList(setup_file_list);
2553 first_valid_list_entry = setup_file_list->next;
2555 /* free empty list header */
2556 setup_file_list->next = NULL;
2557 freeSetupFileList(setup_file_list);
2559 return first_valid_list_entry;
2562 SetupFileHash *loadSetupFileHash(char *filename)
2564 SetupFileHash *setup_file_hash = newSetupFileHash();
2566 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2568 freeSetupFileHash(setup_file_hash);
2573 return setup_file_hash;
2576 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2577 char *filename, char *identifier)
2579 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2582 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2583 else if (!checkCookieString(value, identifier))
2584 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2588 /* ========================================================================= */
2589 /* setup file stuff */
2590 /* ========================================================================= */
2592 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2593 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2594 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2596 /* level directory info */
2597 #define LEVELINFO_TOKEN_IDENTIFIER 0
2598 #define LEVELINFO_TOKEN_NAME 1
2599 #define LEVELINFO_TOKEN_NAME_SORTING 2
2600 #define LEVELINFO_TOKEN_AUTHOR 3
2601 #define LEVELINFO_TOKEN_YEAR 4
2602 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2603 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2604 #define LEVELINFO_TOKEN_TESTED_BY 7
2605 #define LEVELINFO_TOKEN_LEVELS 8
2606 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2607 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2608 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2609 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2610 #define LEVELINFO_TOKEN_READONLY 13
2611 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2612 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2613 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2614 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2615 #define LEVELINFO_TOKEN_MUSIC_SET 18
2616 #define LEVELINFO_TOKEN_FILENAME 19
2617 #define LEVELINFO_TOKEN_FILETYPE 20
2618 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2619 #define LEVELINFO_TOKEN_HANDICAP 22
2620 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2622 #define NUM_LEVELINFO_TOKENS 24
2624 static LevelDirTree ldi;
2626 static struct TokenInfo levelinfo_tokens[] =
2628 /* level directory info */
2629 { TYPE_STRING, &ldi.identifier, "identifier" },
2630 { TYPE_STRING, &ldi.name, "name" },
2631 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2632 { TYPE_STRING, &ldi.author, "author" },
2633 { TYPE_STRING, &ldi.year, "year" },
2634 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2635 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2636 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2637 { TYPE_INTEGER, &ldi.levels, "levels" },
2638 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2639 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2640 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2641 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2642 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2643 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2644 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2645 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2646 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2647 { TYPE_STRING, &ldi.music_set, "music_set" },
2648 { TYPE_STRING, &ldi.level_filename, "filename" },
2649 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2650 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2651 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2652 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2655 static struct TokenInfo artworkinfo_tokens[] =
2657 /* artwork directory info */
2658 { TYPE_STRING, &ldi.identifier, "identifier" },
2659 { TYPE_STRING, &ldi.subdir, "subdir" },
2660 { TYPE_STRING, &ldi.name, "name" },
2661 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2662 { TYPE_STRING, &ldi.author, "author" },
2663 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2664 { TYPE_STRING, &ldi.basepath, "basepath" },
2665 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2666 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2667 { TYPE_INTEGER, &ldi.color, "color" },
2668 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2673 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2677 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2678 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2679 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2680 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2683 ti->node_parent = NULL;
2684 ti->node_group = NULL;
2691 ti->fullpath = NULL;
2692 ti->basepath = NULL;
2693 ti->identifier = NULL;
2694 ti->name = getStringCopy(ANONYMOUS_NAME);
2695 ti->name_sorting = NULL;
2696 ti->author = getStringCopy(ANONYMOUS_NAME);
2699 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2700 ti->latest_engine = FALSE; /* default: get from level */
2701 ti->parent_link = FALSE;
2702 ti->in_user_dir = FALSE;
2703 ti->user_defined = FALSE;
2705 ti->class_desc = NULL;
2707 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2709 if (ti->type == TREE_TYPE_LEVEL_DIR)
2711 ti->imported_from = NULL;
2712 ti->imported_by = NULL;
2713 ti->tested_by = NULL;
2715 ti->graphics_set_ecs = NULL;
2716 ti->graphics_set_aga = NULL;
2717 ti->graphics_set = NULL;
2718 ti->sounds_set = NULL;
2719 ti->music_set = NULL;
2720 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2721 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2722 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2724 ti->level_filename = NULL;
2725 ti->level_filetype = NULL;
2727 ti->special_flags = NULL;
2730 ti->first_level = 0;
2732 ti->level_group = FALSE;
2733 ti->handicap_level = 0;
2734 ti->readonly = TRUE;
2735 ti->handicap = TRUE;
2736 ti->skip_levels = FALSE;
2740 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2744 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2746 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2751 /* copy all values from the parent structure */
2753 ti->type = parent->type;
2755 ti->node_top = parent->node_top;
2756 ti->node_parent = parent;
2757 ti->node_group = NULL;
2764 ti->fullpath = NULL;
2765 ti->basepath = NULL;
2766 ti->identifier = NULL;
2767 ti->name = getStringCopy(ANONYMOUS_NAME);
2768 ti->name_sorting = NULL;
2769 ti->author = getStringCopy(parent->author);
2770 ti->year = getStringCopy(parent->year);
2772 ti->sort_priority = parent->sort_priority;
2773 ti->latest_engine = parent->latest_engine;
2774 ti->parent_link = FALSE;
2775 ti->in_user_dir = parent->in_user_dir;
2776 ti->user_defined = parent->user_defined;
2777 ti->color = parent->color;
2778 ti->class_desc = getStringCopy(parent->class_desc);
2780 ti->infotext = getStringCopy(parent->infotext);
2782 if (ti->type == TREE_TYPE_LEVEL_DIR)
2784 ti->imported_from = getStringCopy(parent->imported_from);
2785 ti->imported_by = getStringCopy(parent->imported_by);
2786 ti->tested_by = getStringCopy(parent->tested_by);
2788 ti->graphics_set_ecs = NULL;
2789 ti->graphics_set_aga = NULL;
2790 ti->graphics_set = NULL;
2791 ti->sounds_set = NULL;
2792 ti->music_set = NULL;
2793 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2794 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2795 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2797 ti->level_filename = NULL;
2798 ti->level_filetype = NULL;
2800 ti->special_flags = getStringCopy(parent->special_flags);
2803 ti->first_level = 0;
2805 ti->level_group = FALSE;
2806 ti->handicap_level = 0;
2808 ti->readonly = parent->readonly;
2810 ti->readonly = TRUE;
2812 ti->handicap = TRUE;
2813 ti->skip_levels = FALSE;
2817 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2819 TreeInfo *ti_copy = newTreeInfo();
2821 /* copy all values from the original structure */
2823 ti_copy->type = ti->type;
2825 ti_copy->node_top = ti->node_top;
2826 ti_copy->node_parent = ti->node_parent;
2827 ti_copy->node_group = ti->node_group;
2828 ti_copy->next = ti->next;
2830 ti_copy->cl_first = ti->cl_first;
2831 ti_copy->cl_cursor = ti->cl_cursor;
2833 ti_copy->subdir = getStringCopy(ti->subdir);
2834 ti_copy->fullpath = getStringCopy(ti->fullpath);
2835 ti_copy->basepath = getStringCopy(ti->basepath);
2836 ti_copy->identifier = getStringCopy(ti->identifier);
2837 ti_copy->name = getStringCopy(ti->name);
2838 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2839 ti_copy->author = getStringCopy(ti->author);
2840 ti_copy->year = getStringCopy(ti->year);
2841 ti_copy->imported_from = getStringCopy(ti->imported_from);
2842 ti_copy->imported_by = getStringCopy(ti->imported_by);
2843 ti_copy->tested_by = getStringCopy(ti->tested_by);
2845 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2846 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2847 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2848 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2849 ti_copy->music_set = getStringCopy(ti->music_set);
2850 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2851 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2852 ti_copy->music_path = getStringCopy(ti->music_path);
2854 ti_copy->level_filename = getStringCopy(ti->level_filename);
2855 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2857 ti_copy->special_flags = getStringCopy(ti->special_flags);
2859 ti_copy->levels = ti->levels;
2860 ti_copy->first_level = ti->first_level;
2861 ti_copy->last_level = ti->last_level;
2862 ti_copy->sort_priority = ti->sort_priority;
2864 ti_copy->latest_engine = ti->latest_engine;
2866 ti_copy->level_group = ti->level_group;
2867 ti_copy->parent_link = ti->parent_link;
2868 ti_copy->in_user_dir = ti->in_user_dir;
2869 ti_copy->user_defined = ti->user_defined;
2870 ti_copy->readonly = ti->readonly;
2871 ti_copy->handicap = ti->handicap;
2872 ti_copy->skip_levels = ti->skip_levels;
2874 ti_copy->color = ti->color;
2875 ti_copy->class_desc = getStringCopy(ti->class_desc);
2876 ti_copy->handicap_level = ti->handicap_level;
2878 ti_copy->infotext = getStringCopy(ti->infotext);
2883 void freeTreeInfo(TreeInfo *ti)
2888 checked_free(ti->subdir);
2889 checked_free(ti->fullpath);
2890 checked_free(ti->basepath);
2891 checked_free(ti->identifier);
2893 checked_free(ti->name);
2894 checked_free(ti->name_sorting);
2895 checked_free(ti->author);
2896 checked_free(ti->year);
2898 checked_free(ti->class_desc);
2900 checked_free(ti->infotext);
2902 if (ti->type == TREE_TYPE_LEVEL_DIR)
2904 checked_free(ti->imported_from);
2905 checked_free(ti->imported_by);
2906 checked_free(ti->tested_by);
2908 checked_free(ti->graphics_set_ecs);
2909 checked_free(ti->graphics_set_aga);
2910 checked_free(ti->graphics_set);
2911 checked_free(ti->sounds_set);
2912 checked_free(ti->music_set);
2914 checked_free(ti->graphics_path);
2915 checked_free(ti->sounds_path);
2916 checked_free(ti->music_path);
2918 checked_free(ti->level_filename);
2919 checked_free(ti->level_filetype);
2921 checked_free(ti->special_flags);
2927 void setSetupInfo(struct TokenInfo *token_info,
2928 int token_nr, char *token_value)
2930 int token_type = token_info[token_nr].type;
2931 void *setup_value = token_info[token_nr].value;
2933 if (token_value == NULL)
2936 /* set setup field to corresponding token value */
2941 *(boolean *)setup_value = get_boolean_from_string(token_value);
2945 *(int *)setup_value = get_switch3_from_string(token_value);
2949 *(Key *)setup_value = getKeyFromKeyName(token_value);
2953 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2957 *(int *)setup_value = get_integer_from_string(token_value);
2961 checked_free(*(char **)setup_value);
2962 *(char **)setup_value = getStringCopy(token_value);
2970 static int compareTreeInfoEntries(const void *object1, const void *object2)
2972 const TreeInfo *entry1 = *((TreeInfo **)object1);
2973 const TreeInfo *entry2 = *((TreeInfo **)object2);
2974 int class_sorting1 = 0, class_sorting2 = 0;
2977 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2979 class_sorting1 = LEVELSORTING(entry1);
2980 class_sorting2 = LEVELSORTING(entry2);
2982 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2983 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2984 entry1->type == TREE_TYPE_MUSIC_DIR)
2986 class_sorting1 = ARTWORKSORTING(entry1);
2987 class_sorting2 = ARTWORKSORTING(entry2);
2990 if (entry1->parent_link || entry2->parent_link)
2991 compare_result = (entry1->parent_link ? -1 : +1);
2992 else if (entry1->sort_priority == entry2->sort_priority)
2994 char *name1 = getStringToLower(entry1->name_sorting);
2995 char *name2 = getStringToLower(entry2->name_sorting);
2997 compare_result = strcmp(name1, name2);
3002 else if (class_sorting1 == class_sorting2)
3003 compare_result = entry1->sort_priority - entry2->sort_priority;
3005 compare_result = class_sorting1 - class_sorting2;
3007 return compare_result;
3010 static void createParentTreeInfoNode(TreeInfo *node_parent)
3014 if (node_parent == NULL)
3017 ti_new = newTreeInfo();
3018 setTreeInfoToDefaults(ti_new, node_parent->type);
3020 ti_new->node_parent = node_parent;
3021 ti_new->parent_link = TRUE;
3023 setString(&ti_new->identifier, node_parent->identifier);
3024 setString(&ti_new->name, ".. (parent directory)");
3025 setString(&ti_new->name_sorting, ti_new->name);
3027 setString(&ti_new->subdir, "..");
3028 setString(&ti_new->fullpath, node_parent->fullpath);
3030 ti_new->sort_priority = node_parent->sort_priority;
3031 ti_new->latest_engine = node_parent->latest_engine;
3033 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3035 pushTreeInfo(&node_parent->node_group, ti_new);
3039 /* -------------------------------------------------------------------------- */
3040 /* functions for handling level and custom artwork info cache */
3041 /* -------------------------------------------------------------------------- */
3043 static void LoadArtworkInfoCache()
3045 InitCacheDirectory();
3047 if (artworkinfo_cache_old == NULL)
3049 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3051 /* try to load artwork info hash from already existing cache file */
3052 artworkinfo_cache_old = loadSetupFileHash(filename);
3054 /* if no artwork info cache file was found, start with empty hash */
3055 if (artworkinfo_cache_old == NULL)
3056 artworkinfo_cache_old = newSetupFileHash();
3061 if (artworkinfo_cache_new == NULL)
3062 artworkinfo_cache_new = newSetupFileHash();
3065 static void SaveArtworkInfoCache()
3067 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3069 InitCacheDirectory();
3071 saveSetupFileHash(artworkinfo_cache_new, filename);
3076 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3078 static char *prefix = NULL;
3080 checked_free(prefix);
3082 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3087 /* (identical to above function, but separate string buffer needed -- nasty) */
3088 static char *getCacheToken(char *prefix, char *suffix)
3090 static char *token = NULL;
3092 checked_free(token);
3094 token = getStringCat2WithSeparator(prefix, suffix, ".");
3099 static char *getFileTimestampString(char *filename)
3102 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3104 struct stat file_status;
3106 if (stat(filename, &file_status) != 0) /* cannot stat file */
3107 return getStringCopy(i_to_a(0));
3109 return getStringCopy(i_to_a(file_status.st_mtime));
3113 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3115 struct stat file_status;
3117 if (timestamp_string == NULL)
3120 if (stat(filename, &file_status) != 0) /* cannot stat file */
3123 return (file_status.st_mtime != atoi(timestamp_string));
3126 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3128 char *identifier = level_node->subdir;
3129 char *type_string = ARTWORK_DIRECTORY(type);
3130 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3131 char *token_main = getCacheToken(token_prefix, "CACHED");
3132 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3133 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3134 TreeInfo *artwork_info = NULL;
3136 if (!use_artworkinfo_cache)
3143 artwork_info = newTreeInfo();
3144 setTreeInfoToDefaults(artwork_info, type);
3146 /* set all structure fields according to the token/value pairs */
3147 ldi = *artwork_info;
3148 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3150 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3151 char *value = getHashEntry(artworkinfo_cache_old, token);
3153 setSetupInfo(artworkinfo_tokens, i, value);
3155 /* check if cache entry for this item is invalid or incomplete */
3159 Error(ERR_WARN, "cache entry '%s' invalid", token);
3166 *artwork_info = ldi;
3171 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3172 LEVELINFO_FILENAME);
3173 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3174 ARTWORKINFO_FILENAME(type));
3176 /* check if corresponding "levelinfo.conf" file has changed */
3177 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3178 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3180 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3183 /* check if corresponding "<artworkinfo>.conf" file has changed */
3184 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3185 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3187 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3192 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
3195 checked_free(filename_levelinfo);
3196 checked_free(filename_artworkinfo);
3199 if (!cached && artwork_info != NULL)
3201 freeTreeInfo(artwork_info);
3206 return artwork_info;
3209 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3210 LevelDirTree *level_node, int type)
3212 char *identifier = level_node->subdir;
3213 char *type_string = ARTWORK_DIRECTORY(type);
3214 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3215 char *token_main = getCacheToken(token_prefix, "CACHED");
3216 boolean set_cache_timestamps = TRUE;
3219 setHashEntry(artworkinfo_cache_new, token_main, "true");
3221 if (set_cache_timestamps)
3223 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3224 LEVELINFO_FILENAME);
3225 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3226 ARTWORKINFO_FILENAME(type));
3227 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3228 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3230 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3231 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3233 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3234 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3236 checked_free(filename_levelinfo);
3237 checked_free(filename_artworkinfo);
3238 checked_free(timestamp_levelinfo);
3239 checked_free(timestamp_artworkinfo);
3242 ldi = *artwork_info;
3243 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3245 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3246 char *value = getSetupValue(artworkinfo_tokens[i].type,
3247 artworkinfo_tokens[i].value);
3249 setHashEntry(artworkinfo_cache_new, token, value);
3254 /* -------------------------------------------------------------------------- */
3255 /* functions for loading level info and custom artwork info */
3256 /* -------------------------------------------------------------------------- */
3258 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3259 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3261 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3262 TreeInfo *node_parent,
3263 char *level_directory,
3264 char *directory_name)
3267 static unsigned int progress_delay = 0;
3268 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3270 char *directory_path = getPath2(level_directory, directory_name);
3271 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3272 SetupFileHash *setup_file_hash;
3273 LevelDirTree *leveldir_new = NULL;
3276 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3277 if (!options.debug && !fileExists(filename))
3279 free(directory_path);
3285 setup_file_hash = loadSetupFileHash(filename);
3287 if (setup_file_hash == NULL)
3289 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3291 free(directory_path);
3297 leveldir_new = newTreeInfo();
3300 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3302 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3304 leveldir_new->subdir = getStringCopy(directory_name);
3306 checkSetupFileHashIdentifier(setup_file_hash, filename,
3307 getCookie("LEVELINFO"));
3309 /* set all structure fields according to the token/value pairs */
3310 ldi = *leveldir_new;
3311 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3312 setSetupInfo(levelinfo_tokens, i,
3313 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3314 *leveldir_new = ldi;
3316 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3317 setString(&leveldir_new->name, leveldir_new->subdir);
3319 if (leveldir_new->identifier == NULL)
3320 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3322 if (leveldir_new->name_sorting == NULL)
3323 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3325 if (node_parent == NULL) /* top level group */
3327 leveldir_new->basepath = getStringCopy(level_directory);
3328 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3330 else /* sub level group */
3332 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3333 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3337 if (leveldir_new->levels < 1)
3338 leveldir_new->levels = 1;
3341 leveldir_new->last_level =
3342 leveldir_new->first_level + leveldir_new->levels - 1;
3344 leveldir_new->in_user_dir =
3345 (!strEqual(leveldir_new->basepath, options.level_directory));
3348 printf("::: '%s' -> %d\n",
3349 leveldir_new->identifier,
3350 leveldir_new->in_user_dir);
3353 /* adjust some settings if user's private level directory was detected */
3354 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3355 leveldir_new->in_user_dir &&
3356 (strEqual(leveldir_new->subdir, getLoginName()) ||
3357 strEqual(leveldir_new->name, getLoginName()) ||
3358 strEqual(leveldir_new->author, getRealName())))
3360 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3361 leveldir_new->readonly = FALSE;
3364 leveldir_new->user_defined =
3365 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3367 leveldir_new->color = LEVELCOLOR(leveldir_new);
3369 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3371 leveldir_new->handicap_level = /* set handicap to default value */
3372 (leveldir_new->user_defined || !leveldir_new->handicap ?
3373 leveldir_new->last_level : leveldir_new->first_level);
3377 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3378 leveldir_new->level_group);
3380 if (leveldir_new->level_group ||
3381 DelayReached(&progress_delay, progress_delay_value))
3382 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3385 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3389 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3391 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3393 /* skip level sets without levels (which are probably artwork base sets) */
3395 freeSetupFileHash(setup_file_hash);
3396 free(directory_path);
3404 pushTreeInfo(node_first, leveldir_new);
3406 freeSetupFileHash(setup_file_hash);
3408 if (leveldir_new->level_group)
3410 /* create node to link back to current level directory */
3411 createParentTreeInfoNode(leveldir_new);
3413 /* recursively step into sub-directory and look for more level series */
3414 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3415 leveldir_new, directory_path);
3418 free(directory_path);
3425 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3426 TreeInfo *node_parent,
3427 char *level_directory)
3430 DirectoryEntry *dir_entry;
3431 boolean valid_entry_found = FALSE;
3434 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3437 if ((dir = openDirectory(level_directory)) == NULL)
3439 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3445 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3448 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3450 char *directory_name = dir_entry->basename;
3451 char *directory_path = getPath2(level_directory, directory_name);
3454 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3457 /* skip entries for current and parent directory */
3458 if (strEqual(directory_name, ".") ||
3459 strEqual(directory_name, ".."))
3461 free(directory_path);
3467 /* find out if directory entry is itself a directory */
3468 if (!dir_entry->is_directory) /* not a directory */
3470 free(directory_path);
3473 Error(ERR_INFO, "* entry '%s' is not a directory ...", directory_name);
3479 /* find out if directory entry is itself a directory */
3480 struct stat file_status;
3481 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3482 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3484 free(directory_path);
3490 free(directory_path);
3492 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3493 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3494 strEqual(directory_name, MUSIC_DIRECTORY))
3497 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3502 closeDirectory(dir);
3504 /* special case: top level directory may directly contain "levelinfo.conf" */
3505 if (node_parent == NULL && !valid_entry_found)
3507 /* check if this directory directly contains a file "levelinfo.conf" */
3508 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3509 level_directory, ".");
3512 if (!valid_entry_found)
3513 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3519 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3520 TreeInfo *node_parent,
3521 char *level_directory)
3524 struct dirent *dir_entry;
3525 boolean valid_entry_found = FALSE;
3528 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3531 if ((dir = opendir(level_directory)) == NULL)
3533 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3539 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3542 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3544 struct stat file_status;
3545 char *directory_name = dir_entry->d_name;
3546 char *directory_path = getPath2(level_directory, directory_name);
3549 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3552 /* skip entries for current and parent directory */
3553 if (strEqual(directory_name, ".") ||
3554 strEqual(directory_name, ".."))
3556 free(directory_path);
3560 /* find out if directory entry is itself a directory */
3561 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3562 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3564 free(directory_path);
3568 free(directory_path);
3570 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3571 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3572 strEqual(directory_name, MUSIC_DIRECTORY))
3575 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3582 /* special case: top level directory may directly contain "levelinfo.conf" */
3583 if (node_parent == NULL && !valid_entry_found)
3585 /* check if this directory directly contains a file "levelinfo.conf" */
3586 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3587 level_directory, ".");
3590 if (!valid_entry_found)
3591 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3596 boolean AdjustGraphicsForEMC()
3598 boolean settings_changed = FALSE;
3600 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3601 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3603 return settings_changed;
3606 void LoadLevelInfo()
3608 InitUserLevelDirectory(getLoginName());
3610 DrawInitText("Loading level series", 120, FC_GREEN);
3612 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3613 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3615 /* after loading all level set information, clone the level directory tree
3616 and remove all level sets without levels (these may still contain artwork
3617 to be offered in the setup menu as "custom artwork", and are therefore
3618 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3619 leveldir_first_all = leveldir_first;
3620 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3622 AdjustGraphicsForEMC();
3624 /* before sorting, the first entries will be from the user directory */
3625 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3627 if (leveldir_first == NULL)
3628 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3630 sortTreeInfo(&leveldir_first);
3633 dumpTreeInfo(leveldir_first, 0);
3637 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3638 TreeInfo *node_parent,
3639 char *base_directory,
3640 char *directory_name, int type)
3642 char *directory_path = getPath2(base_directory, directory_name);
3643 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3644 SetupFileHash *setup_file_hash = NULL;
3645 TreeInfo *artwork_new = NULL;
3648 if (fileExists(filename))
3649 setup_file_hash = loadSetupFileHash(filename);
3651 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3654 struct dirent *dir_entry;
3655 boolean valid_file_found = FALSE;
3657 if ((dir = opendir(directory_path)) != NULL)
3659 while ((dir_entry = readdir(dir)) != NULL)
3661 char *entry_name = dir_entry->d_name;
3663 if (FileIsArtworkType(entry_name, type))
3665 valid_file_found = TRUE;
3673 if (!valid_file_found)
3675 if (!strEqual(directory_name, "."))
3676 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3678 free(directory_path);
3685 artwork_new = newTreeInfo();
3688 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3690 setTreeInfoToDefaults(artwork_new, type);
3692 artwork_new->subdir = getStringCopy(directory_name);
3694 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3697 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3700 /* set all structure fields according to the token/value pairs */
3702 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3703 setSetupInfo(levelinfo_tokens, i,
3704 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3707 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3708 setString(&artwork_new->name, artwork_new->subdir);
3710 if (artwork_new->identifier == NULL)
3711 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3713 if (artwork_new->name_sorting == NULL)
3714 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3717 if (node_parent == NULL) /* top level group */
3719 artwork_new->basepath = getStringCopy(base_directory);
3720 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3722 else /* sub level group */
3724 artwork_new->basepath = getStringCopy(node_parent->basepath);
3725 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3728 artwork_new->in_user_dir =
3729 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3731 /* (may use ".sort_priority" from "setup_file_hash" above) */
3732 artwork_new->color = ARTWORKCOLOR(artwork_new);
3734 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3736 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3738 if (strEqual(artwork_new->subdir, "."))
3740 if (artwork_new->user_defined)
3742 setString(&artwork_new->identifier, "private");
3743 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3747 setString(&artwork_new->identifier, "classic");
3748 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3751 /* set to new values after changing ".sort_priority" */
3752 artwork_new->color = ARTWORKCOLOR(artwork_new);
3754 setString(&artwork_new->class_desc,
3755 getLevelClassDescription(artwork_new));
3759 setString(&artwork_new->identifier, artwork_new->subdir);
3762 setString(&artwork_new->name, artwork_new->identifier);
3763 setString(&artwork_new->name_sorting, artwork_new->name);
3767 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3770 pushTreeInfo(node_first, artwork_new);
3772 freeSetupFileHash(setup_file_hash);
3774 free(directory_path);
3780 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3781 TreeInfo *node_parent,
3782 char *base_directory, int type)
3785 struct dirent *dir_entry;
3786 boolean valid_entry_found = FALSE;
3788 if ((dir = opendir(base_directory)) == NULL)
3790 /* display error if directory is main "options.graphics_directory" etc. */
3791 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3792 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3797 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3799 struct stat file_status;
3800 char *directory_name = dir_entry->d_name;
3801 char *directory_path = getPath2(base_directory, directory_name);
3803 /* skip directory entries for current and parent directory */
3804 if (strEqual(directory_name, ".") ||
3805 strEqual(directory_name, ".."))
3807 free(directory_path);
3811 /* skip directory entries which are not a directory or are not accessible */
3812 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3813 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3815 free(directory_path);
3819 free(directory_path);
3821 /* check if this directory contains artwork with or without config file */
3822 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3824 directory_name, type);
3829 /* check if this directory directly contains artwork itself */
3830 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3831 base_directory, ".",
3833 if (!valid_entry_found)
3834 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3838 static TreeInfo *getDummyArtworkInfo(int type)
3840 /* this is only needed when there is completely no artwork available */
3841 TreeInfo *artwork_new = newTreeInfo();
3843 setTreeInfoToDefaults(artwork_new, type);
3845 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3846 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3847 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3849 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3850 setString(&artwork_new->name, UNDEFINED_FILENAME);
3851 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3856 void LoadArtworkInfo()
3858 LoadArtworkInfoCache();
3860 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3862 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3863 options.graphics_directory,
3864 TREE_TYPE_GRAPHICS_DIR);
3865 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3866 getUserGraphicsDir(),
3867 TREE_TYPE_GRAPHICS_DIR);
3869 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3870 options.sounds_directory,
3871 TREE_TYPE_SOUNDS_DIR);
3872 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3874 TREE_TYPE_SOUNDS_DIR);
3876 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3877 options.music_directory,
3878 TREE_TYPE_MUSIC_DIR);
3879 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3881 TREE_TYPE_MUSIC_DIR);
3883 if (artwork.gfx_first == NULL)
3884 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3885 if (artwork.snd_first == NULL)
3886 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3887 if (artwork.mus_first == NULL)
3888 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3890 /* before sorting, the first entries will be from the user directory */
3891 artwork.gfx_current =
3892 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3893 if (artwork.gfx_current == NULL)
3894 artwork.gfx_current =
3895 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3896 if (artwork.gfx_current == NULL)
3897 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3899 artwork.snd_current =
3900 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3901 if (artwork.snd_current == NULL)
3902 artwork.snd_current =
3903 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3904 if (artwork.snd_current == NULL)
3905 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3907 artwork.mus_current =
3908 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3909 if (artwork.mus_current == NULL)
3910 artwork.mus_current =
3911 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3912 if (artwork.mus_current == NULL)
3913 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3915 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3916 artwork.snd_current_identifier = artwork.snd_current->identifier;
3917 artwork.mus_current_identifier = artwork.mus_current->identifier;
3920 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3921 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3922 printf("music set == %s\n\n", artwork.mus_current_identifier);
3925 sortTreeInfo(&artwork.gfx_first);
3926 sortTreeInfo(&artwork.snd_first);
3927 sortTreeInfo(&artwork.mus_first);
3930 dumpTreeInfo(artwork.gfx_first, 0);
3931 dumpTreeInfo(artwork.snd_first, 0);
3932 dumpTreeInfo(artwork.mus_first, 0);
3936 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3937 LevelDirTree *level_node)
3940 static unsigned int progress_delay = 0;
3941 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3943 int type = (*artwork_node)->type;
3945 /* recursively check all level directories for artwork sub-directories */
3949 /* check all tree entries for artwork, but skip parent link entries */
3950 if (!level_node->parent_link)
3952 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3953 boolean cached = (artwork_new != NULL);
3957 pushTreeInfo(artwork_node, artwork_new);
3961 TreeInfo *topnode_last = *artwork_node;
3962 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3963 ARTWORK_DIRECTORY(type));
3965 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3967 if (topnode_last != *artwork_node) /* check for newly added node */
3969 artwork_new = *artwork_node;
3971 setString(&artwork_new->identifier, level_node->subdir);
3972 setString(&artwork_new->name, level_node->name);
3973 setString(&artwork_new->name_sorting, level_node->name_sorting);
3975 artwork_new->sort_priority = level_node->sort_priority;
3976 artwork_new->color = LEVELCOLOR(artwork_new);
3982 /* insert artwork info (from old cache or filesystem) into new cache */
3983 if (artwork_new != NULL)
3984 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3988 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3989 level_node->level_group);
3991 if (level_node->level_group ||
3992 DelayReached(&progress_delay, progress_delay_value))
3993 DrawInitText(level_node->name, 150, FC_YELLOW);
3996 if (level_node->node_group != NULL)
3997 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3999 level_node = level_node->next;
4003 void LoadLevelArtworkInfo()
4005 print_timestamp_init("LoadLevelArtworkInfo");
4007 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4009 print_timestamp_time("DrawTimeText");
4011 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
4012 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4013 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
4014 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4015 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
4016 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4018 SaveArtworkInfoCache();
4020 print_timestamp_time("SaveArtworkInfoCache");
4022 /* needed for reloading level artwork not known at ealier stage */
4024 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
4026 artwork.gfx_current =
4027 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4028 if (artwork.gfx_current == NULL)
4029 artwork.gfx_current =
4030 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4031 if (artwork.gfx_current == NULL)
4032 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4035 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
4037 artwork.snd_current =
4038 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4039 if (artwork.snd_current == NULL)
4040 artwork.snd_current =
4041 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4042 if (artwork.snd_current == NULL)
4043 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4046 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
4048 artwork.mus_current =
4049 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4050 if (artwork.mus_current == NULL)
4051 artwork.mus_current =
4052 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4053 if (artwork.mus_current == NULL)
4054 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4057 print_timestamp_time("getTreeInfoFromIdentifier");
4059 sortTreeInfo(&artwork.gfx_first);
4060 sortTreeInfo(&artwork.snd_first);
4061 sortTreeInfo(&artwork.mus_first);
4063 print_timestamp_time("sortTreeInfo");
4066 dumpTreeInfo(artwork.gfx_first, 0);
4067 dumpTreeInfo(artwork.snd_first, 0);
4068 dumpTreeInfo(artwork.mus_first, 0);
4071 print_timestamp_done("LoadLevelArtworkInfo");
4074 static void SaveUserLevelInfo()
4076 LevelDirTree *level_info;
4081 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
4083 if (!(file = fopen(filename, MODE_WRITE)))
4085 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4090 level_info = newTreeInfo();
4092 /* always start with reliable default values */
4093 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4095 setString(&level_info->name, getLoginName());
4096 setString(&level_info->author, getRealName());
4097 level_info->levels = 100;
4098 level_info->first_level = 1;
4100 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4102 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4103 getCookie("LEVELINFO")));
4106 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4108 if (i == LEVELINFO_TOKEN_NAME ||
4109 i == LEVELINFO_TOKEN_AUTHOR ||
4110 i == LEVELINFO_TOKEN_LEVELS ||
4111 i == LEVELINFO_TOKEN_FIRST_LEVEL)
4112 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4114 /* just to make things nicer :) */
4115 if (i == LEVELINFO_TOKEN_AUTHOR)
4116 fprintf(file, "\n");
4119 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4123 SetFilePermissions(filename, PERMS_PRIVATE);
4125 freeTreeInfo(level_info);
4129 char *getSetupValue(int type, void *value)
4131 static char value_string[MAX_LINE_LEN];
4139 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4143 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4147 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4148 *(int *)value == FALSE ? "off" : "on"));
4152 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4155 case TYPE_YES_NO_AUTO:
4156 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4157 *(int *)value == FALSE ? "no" : "yes"));
4161 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4165 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4169 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4173 sprintf(value_string, "%d", *(int *)value);
4177 if (*(char **)value == NULL)
4180 strcpy(value_string, *(char **)value);
4184 value_string[0] = '\0';
4188 if (type & TYPE_GHOSTED)
4189 strcpy(value_string, "n/a");
4191 return value_string;
4194 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4198 static char token_string[MAX_LINE_LEN];
4199 int token_type = token_info[token_nr].type;
4200 void *setup_value = token_info[token_nr].value;
4201 char *token_text = token_info[token_nr].text;
4202 char *value_string = getSetupValue(token_type, setup_value);
4204 /* build complete token string */
4205 sprintf(token_string, "%s%s", prefix, token_text);
4207 /* build setup entry line */
4208 line = getFormattedSetupEntry(token_string, value_string);
4210 if (token_type == TYPE_KEY_X11)
4212 Key key = *(Key *)setup_value;
4213 char *keyname = getKeyNameFromKey(key);
4215 /* add comment, if useful */
4216 if (!strEqual(keyname, "(undefined)") &&
4217 !strEqual(keyname, "(unknown)"))
4219 /* add at least one whitespace */
4221 for (i = strlen(line); i < token_comment_position; i++)
4225 strcat(line, keyname);
4232 void LoadLevelSetup_LastSeries()
4234 /* ----------------------------------------------------------------------- */
4235 /* ~/.<program>/levelsetup.conf */
4236 /* ----------------------------------------------------------------------- */
4238 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4239 SetupFileHash *level_setup_hash = NULL;
4241 /* always start with reliable default values */
4242 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4244 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
4245 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4247 if (leveldir_current == NULL)
4248 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4251 if ((level_setup_hash = loadSetupFileHash(filename)))
4253 char *last_level_series =
4254 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4256 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4258 if (leveldir_current == NULL)
4259 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4261 checkSetupFileHashIdentifier(level_setup_hash, filename,
4262 getCookie("LEVELSETUP"));
4264 freeSetupFileHash(level_setup_hash);
4267 Error(ERR_WARN, "using default setup values");
4272 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4274 /* ----------------------------------------------------------------------- */
4275 /* ~/.<program>/levelsetup.conf */
4276 /* ----------------------------------------------------------------------- */
4278 // check if the current level directory structure is available at this point
4279 if (leveldir_current == NULL)
4282 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4283 char *level_subdir = leveldir_current->subdir;
4286 InitUserDataDirectory();
4288 if (!(file = fopen(filename, MODE_WRITE)))
4290 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4297 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4298 getCookie("LEVELSETUP")));
4300 if (deactivate_last_level_series)
4301 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4303 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4308 SetFilePermissions(filename, PERMS_PRIVATE);
4313 void SaveLevelSetup_LastSeries()
4315 SaveLevelSetup_LastSeries_Ext(FALSE);
4318 void SaveLevelSetup_LastSeries_Deactivate()
4320 SaveLevelSetup_LastSeries_Ext(TRUE);
4325 static void checkSeriesInfo()
4327 static char *level_directory = NULL;
4330 DirectoryEntry *dir_entry;
4333 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4335 level_directory = getPath2((leveldir_current->in_user_dir ?
4336 getUserLevelDir(NULL) :
4337 options.level_directory),
4338 leveldir_current->fullpath);
4340 if ((dir = openDirectory(level_directory)) == NULL)
4342 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4348 while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */
4350 if (strlen(dir_entry->basename) > 4 &&
4351 dir_entry->basename[3] == '.' &&
4352 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4354 char levelnum_str[4];
4357 strncpy(levelnum_str, dir_entry->basename, 3);
4358 levelnum_str[3] = '\0';
4360 levelnum_value = atoi(levelnum_str);
4362 if (levelnum_value < leveldir_current->first_level)
4364 Error(ERR_WARN, "additional level %d found", levelnum_value);
4365 leveldir_current->first_level = levelnum_value;
4367 else if (levelnum_value > leveldir_current->last_level)
4369 Error(ERR_WARN, "additional level %d found", levelnum_value);
4370 leveldir_current->last_level = levelnum_value;
4376 closeDirectory(dir);
4381 static void checkSeriesInfo()
4383 static char *level_directory = NULL;
4386 struct dirent *dir_entry;
4389 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4391 level_directory = getPath2((leveldir_current->in_user_dir ?
4392 getUserLevelDir(NULL) :
4393 options.level_directory),
4394 leveldir_current->fullpath);
4396 if ((dir = opendir(level_directory)) == NULL)
4398 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4404 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4406 if (strlen(dir_entry->d_name) > 4 &&
4407 dir_entry->d_name[3] == '.' &&
4408 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4410 char levelnum_str[4];
4413 strncpy(levelnum_str, dir_entry->d_name, 3);
4414 levelnum_str[3] = '\0';
4416 levelnum_value = atoi(levelnum_str);
4418 if (levelnum_value < leveldir_current->first_level)
4420 Error(ERR_WARN, "additional level %d found", levelnum_value);
4421 leveldir_current->first_level = levelnum_value;
4423 else if (levelnum_value > leveldir_current->last_level)
4425 Error(ERR_WARN, "additional level %d found", levelnum_value);
4426 leveldir_current->last_level = levelnum_value;
4437 void LoadLevelSetup_SeriesInfo()
4440 SetupFileHash *level_setup_hash = NULL;
4441 char *level_subdir = leveldir_current->subdir;
4444 /* always start with reliable default values */
4445 level_nr = leveldir_current->first_level;
4447 for (i = 0; i < MAX_LEVELS; i++)
4449 LevelStats_setPlayed(i, 0);
4450 LevelStats_setSolved(i, 0);
4453 checkSeriesInfo(leveldir_current);
4455 /* ----------------------------------------------------------------------- */
4456 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4457 /* ----------------------------------------------------------------------- */
4459 level_subdir = leveldir_current->subdir;
4461 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4463 if ((level_setup_hash = loadSetupFileHash(filename)))
4467 /* get last played level in this level set */
4469 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4473 level_nr = atoi(token_value);
4475 if (level_nr < leveldir_current->first_level)
4476 level_nr = leveldir_current->first_level;
4477 if (level_nr > leveldir_current->last_level)
4478 level_nr = leveldir_current->last_level;
4481 /* get handicap level in this level set */
4483 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4487 int level_nr = atoi(token_value);
4489 if (level_nr < leveldir_current->first_level)
4490 level_nr = leveldir_current->first_level;
4491 if (level_nr > leveldir_current->last_level + 1)
4492 level_nr = leveldir_current->last_level;
4494 if (leveldir_current->user_defined || !leveldir_current->handicap)
4495 level_nr = leveldir_current->last_level;
4497 leveldir_current->handicap_level = level_nr;
4500 /* get number of played and solved levels in this level set */
4502 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4504 char *token = HASH_ITERATION_TOKEN(itr);
4505 char *value = HASH_ITERATION_VALUE(itr);
4507 if (strlen(token) == 3 &&
4508 token[0] >= '0' && token[0] <= '9' &&
4509 token[1] >= '0' && token[1] <= '9' &&
4510 token[2] >= '0' && token[2] <= '9')
4512 int level_nr = atoi(token);
4515 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4517 value = strchr(value, ' ');
4520 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4523 END_HASH_ITERATION(hash, itr)
4525 checkSetupFileHashIdentifier(level_setup_hash, filename,
4526 getCookie("LEVELSETUP"));
4528 freeSetupFileHash(level_setup_hash);
4531 Error(ERR_WARN, "using default setup values");
4536 void SaveLevelSetup_SeriesInfo()
4539 char *level_subdir = leveldir_current->subdir;
4540 char *level_nr_str = int2str(level_nr, 0);
4541 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4545 /* ----------------------------------------------------------------------- */
4546 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4547 /* ----------------------------------------------------------------------- */
4549 InitLevelSetupDirectory(level_subdir);
4551 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4553 if (!(file = fopen(filename, MODE_WRITE)))
4555 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4560 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4561 getCookie("LEVELSETUP")));
4562 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4564 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4565 handicap_level_str));
4567 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4570 if (LevelStats_getPlayed(i) > 0 ||
4571 LevelStats_getSolved(i) > 0)
4576 sprintf(token, "%03d", i);
4577 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4579 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4585 SetFilePermissions(filename, PERMS_PRIVATE);
4590 int LevelStats_getPlayed(int nr)
4592 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4595 int LevelStats_getSolved(int nr)
4597 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4600 void LevelStats_setPlayed(int nr, int value)
4602 if (nr >= 0 && nr < MAX_LEVELS)
4603 level_stats[nr].played = value;
4606 void LevelStats_setSolved(int nr, int value)
4608 if (nr >= 0 && nr < MAX_LEVELS)
4609 level_stats[nr].solved = value;
4612 void LevelStats_incPlayed(int nr)
4614 if (nr >= 0 && nr < MAX_LEVELS)
4615 level_stats[nr].played++;
4618 void LevelStats_incSolved(int nr)
4620 if (nr >= 0 && nr < MAX_LEVELS)
4621 level_stats[nr].solved++;