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 struct stat file_status;
3451 char *directory_name = dir_entry->basename;
3452 char *directory_path = getPath2(level_directory, directory_name);
3455 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3458 /* skip entries for current and parent directory */
3459 if (strEqual(directory_name, ".") ||
3460 strEqual(directory_name, ".."))
3462 free(directory_path);
3468 /* find out if directory entry is itself a directory */
3469 if (!dir_entry->is_directory) /* not a directory */
3471 free(directory_path);
3476 /* find out if directory entry is itself a directory */
3477 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3478 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3480 free(directory_path);
3486 free(directory_path);
3488 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3489 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3490 strEqual(directory_name, MUSIC_DIRECTORY))
3493 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3498 closeDirectory(dir);
3500 /* special case: top level directory may directly contain "levelinfo.conf" */
3501 if (node_parent == NULL && !valid_entry_found)
3503 /* check if this directory directly contains a file "levelinfo.conf" */
3504 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3505 level_directory, ".");
3508 if (!valid_entry_found)
3509 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3515 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3516 TreeInfo *node_parent,
3517 char *level_directory)
3520 struct dirent *dir_entry;
3521 boolean valid_entry_found = FALSE;
3524 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3527 if ((dir = opendir(level_directory)) == NULL)
3529 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3535 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3538 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3540 struct stat file_status;
3541 char *directory_name = dir_entry->d_name;
3542 char *directory_path = getPath2(level_directory, directory_name);
3545 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3548 /* skip entries for current and parent directory */
3549 if (strEqual(directory_name, ".") ||
3550 strEqual(directory_name, ".."))
3552 free(directory_path);
3556 /* find out if directory entry is itself a directory */
3557 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3558 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3560 free(directory_path);
3564 free(directory_path);
3566 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3567 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3568 strEqual(directory_name, MUSIC_DIRECTORY))
3571 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3578 /* special case: top level directory may directly contain "levelinfo.conf" */
3579 if (node_parent == NULL && !valid_entry_found)
3581 /* check if this directory directly contains a file "levelinfo.conf" */
3582 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3583 level_directory, ".");
3586 if (!valid_entry_found)
3587 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3592 boolean AdjustGraphicsForEMC()
3594 boolean settings_changed = FALSE;
3596 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3597 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3599 return settings_changed;
3602 void LoadLevelInfo()
3604 InitUserLevelDirectory(getLoginName());
3606 DrawInitText("Loading level series", 120, FC_GREEN);
3608 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3609 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3611 /* after loading all level set information, clone the level directory tree
3612 and remove all level sets without levels (these may still contain artwork
3613 to be offered in the setup menu as "custom artwork", and are therefore
3614 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3615 leveldir_first_all = leveldir_first;
3616 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3618 AdjustGraphicsForEMC();
3620 /* before sorting, the first entries will be from the user directory */
3621 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3623 if (leveldir_first == NULL)
3624 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3626 sortTreeInfo(&leveldir_first);
3629 dumpTreeInfo(leveldir_first, 0);
3633 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3634 TreeInfo *node_parent,
3635 char *base_directory,
3636 char *directory_name, int type)
3638 char *directory_path = getPath2(base_directory, directory_name);
3639 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3640 SetupFileHash *setup_file_hash = NULL;
3641 TreeInfo *artwork_new = NULL;
3644 if (fileExists(filename))
3645 setup_file_hash = loadSetupFileHash(filename);
3647 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3650 struct dirent *dir_entry;
3651 boolean valid_file_found = FALSE;
3653 if ((dir = opendir(directory_path)) != NULL)
3655 while ((dir_entry = readdir(dir)) != NULL)
3657 char *entry_name = dir_entry->d_name;
3659 if (FileIsArtworkType(entry_name, type))
3661 valid_file_found = TRUE;
3669 if (!valid_file_found)
3671 if (!strEqual(directory_name, "."))
3672 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3674 free(directory_path);
3681 artwork_new = newTreeInfo();
3684 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3686 setTreeInfoToDefaults(artwork_new, type);
3688 artwork_new->subdir = getStringCopy(directory_name);
3690 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3693 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3696 /* set all structure fields according to the token/value pairs */
3698 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3699 setSetupInfo(levelinfo_tokens, i,
3700 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3703 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3704 setString(&artwork_new->name, artwork_new->subdir);
3706 if (artwork_new->identifier == NULL)
3707 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3709 if (artwork_new->name_sorting == NULL)
3710 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3713 if (node_parent == NULL) /* top level group */
3715 artwork_new->basepath = getStringCopy(base_directory);
3716 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3718 else /* sub level group */
3720 artwork_new->basepath = getStringCopy(node_parent->basepath);
3721 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3724 artwork_new->in_user_dir =
3725 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3727 /* (may use ".sort_priority" from "setup_file_hash" above) */
3728 artwork_new->color = ARTWORKCOLOR(artwork_new);
3730 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3732 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3734 if (strEqual(artwork_new->subdir, "."))
3736 if (artwork_new->user_defined)
3738 setString(&artwork_new->identifier, "private");
3739 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3743 setString(&artwork_new->identifier, "classic");
3744 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3747 /* set to new values after changing ".sort_priority" */
3748 artwork_new->color = ARTWORKCOLOR(artwork_new);
3750 setString(&artwork_new->class_desc,
3751 getLevelClassDescription(artwork_new));
3755 setString(&artwork_new->identifier, artwork_new->subdir);
3758 setString(&artwork_new->name, artwork_new->identifier);
3759 setString(&artwork_new->name_sorting, artwork_new->name);
3763 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3766 pushTreeInfo(node_first, artwork_new);
3768 freeSetupFileHash(setup_file_hash);
3770 free(directory_path);
3776 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3777 TreeInfo *node_parent,
3778 char *base_directory, int type)
3781 struct dirent *dir_entry;
3782 boolean valid_entry_found = FALSE;
3784 if ((dir = opendir(base_directory)) == NULL)
3786 /* display error if directory is main "options.graphics_directory" etc. */
3787 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3788 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3793 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3795 struct stat file_status;
3796 char *directory_name = dir_entry->d_name;
3797 char *directory_path = getPath2(base_directory, directory_name);
3799 /* skip directory entries for current and parent directory */
3800 if (strEqual(directory_name, ".") ||
3801 strEqual(directory_name, ".."))
3803 free(directory_path);
3807 /* skip directory entries which are not a directory or are not accessible */
3808 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3809 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3811 free(directory_path);
3815 free(directory_path);
3817 /* check if this directory contains artwork with or without config file */
3818 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3820 directory_name, type);
3825 /* check if this directory directly contains artwork itself */
3826 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3827 base_directory, ".",
3829 if (!valid_entry_found)
3830 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3834 static TreeInfo *getDummyArtworkInfo(int type)
3836 /* this is only needed when there is completely no artwork available */
3837 TreeInfo *artwork_new = newTreeInfo();
3839 setTreeInfoToDefaults(artwork_new, type);
3841 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3842 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3843 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3845 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3846 setString(&artwork_new->name, UNDEFINED_FILENAME);
3847 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3852 void LoadArtworkInfo()
3854 LoadArtworkInfoCache();
3856 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3858 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3859 options.graphics_directory,
3860 TREE_TYPE_GRAPHICS_DIR);
3861 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3862 getUserGraphicsDir(),
3863 TREE_TYPE_GRAPHICS_DIR);
3865 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3866 options.sounds_directory,
3867 TREE_TYPE_SOUNDS_DIR);
3868 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3870 TREE_TYPE_SOUNDS_DIR);
3872 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3873 options.music_directory,
3874 TREE_TYPE_MUSIC_DIR);
3875 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3877 TREE_TYPE_MUSIC_DIR);
3879 if (artwork.gfx_first == NULL)
3880 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3881 if (artwork.snd_first == NULL)
3882 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3883 if (artwork.mus_first == NULL)
3884 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3886 /* before sorting, the first entries will be from the user directory */
3887 artwork.gfx_current =
3888 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3889 if (artwork.gfx_current == NULL)
3890 artwork.gfx_current =
3891 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3892 if (artwork.gfx_current == NULL)
3893 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3895 artwork.snd_current =
3896 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3897 if (artwork.snd_current == NULL)
3898 artwork.snd_current =
3899 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3900 if (artwork.snd_current == NULL)
3901 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3903 artwork.mus_current =
3904 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3905 if (artwork.mus_current == NULL)
3906 artwork.mus_current =
3907 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3908 if (artwork.mus_current == NULL)
3909 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3911 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3912 artwork.snd_current_identifier = artwork.snd_current->identifier;
3913 artwork.mus_current_identifier = artwork.mus_current->identifier;
3916 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3917 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3918 printf("music set == %s\n\n", artwork.mus_current_identifier);
3921 sortTreeInfo(&artwork.gfx_first);
3922 sortTreeInfo(&artwork.snd_first);
3923 sortTreeInfo(&artwork.mus_first);
3926 dumpTreeInfo(artwork.gfx_first, 0);
3927 dumpTreeInfo(artwork.snd_first, 0);
3928 dumpTreeInfo(artwork.mus_first, 0);
3932 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3933 LevelDirTree *level_node)
3936 static unsigned int progress_delay = 0;
3937 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3939 int type = (*artwork_node)->type;
3941 /* recursively check all level directories for artwork sub-directories */
3945 /* check all tree entries for artwork, but skip parent link entries */
3946 if (!level_node->parent_link)
3948 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3949 boolean cached = (artwork_new != NULL);
3953 pushTreeInfo(artwork_node, artwork_new);
3957 TreeInfo *topnode_last = *artwork_node;
3958 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3959 ARTWORK_DIRECTORY(type));
3961 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3963 if (topnode_last != *artwork_node) /* check for newly added node */
3965 artwork_new = *artwork_node;
3967 setString(&artwork_new->identifier, level_node->subdir);
3968 setString(&artwork_new->name, level_node->name);
3969 setString(&artwork_new->name_sorting, level_node->name_sorting);
3971 artwork_new->sort_priority = level_node->sort_priority;
3972 artwork_new->color = LEVELCOLOR(artwork_new);
3978 /* insert artwork info (from old cache or filesystem) into new cache */
3979 if (artwork_new != NULL)
3980 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3984 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3985 level_node->level_group);
3987 if (level_node->level_group ||
3988 DelayReached(&progress_delay, progress_delay_value))
3989 DrawInitText(level_node->name, 150, FC_YELLOW);
3992 if (level_node->node_group != NULL)
3993 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3995 level_node = level_node->next;
3999 void LoadLevelArtworkInfo()
4001 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4003 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
4004 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
4005 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
4007 SaveArtworkInfoCache();
4009 /* needed for reloading level artwork not known at ealier stage */
4011 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
4013 artwork.gfx_current =
4014 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4015 if (artwork.gfx_current == NULL)
4016 artwork.gfx_current =
4017 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4018 if (artwork.gfx_current == NULL)
4019 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4022 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
4024 artwork.snd_current =
4025 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4026 if (artwork.snd_current == NULL)
4027 artwork.snd_current =
4028 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4029 if (artwork.snd_current == NULL)
4030 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4033 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
4035 artwork.mus_current =
4036 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4037 if (artwork.mus_current == NULL)
4038 artwork.mus_current =
4039 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4040 if (artwork.mus_current == NULL)
4041 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4044 sortTreeInfo(&artwork.gfx_first);
4045 sortTreeInfo(&artwork.snd_first);
4046 sortTreeInfo(&artwork.mus_first);
4049 dumpTreeInfo(artwork.gfx_first, 0);
4050 dumpTreeInfo(artwork.snd_first, 0);
4051 dumpTreeInfo(artwork.mus_first, 0);
4055 static void SaveUserLevelInfo()
4057 LevelDirTree *level_info;
4062 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
4064 if (!(file = fopen(filename, MODE_WRITE)))
4066 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4071 level_info = newTreeInfo();
4073 /* always start with reliable default values */
4074 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4076 setString(&level_info->name, getLoginName());
4077 setString(&level_info->author, getRealName());
4078 level_info->levels = 100;
4079 level_info->first_level = 1;
4081 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4083 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4084 getCookie("LEVELINFO")));
4087 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4089 if (i == LEVELINFO_TOKEN_NAME ||
4090 i == LEVELINFO_TOKEN_AUTHOR ||
4091 i == LEVELINFO_TOKEN_LEVELS ||
4092 i == LEVELINFO_TOKEN_FIRST_LEVEL)
4093 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4095 /* just to make things nicer :) */
4096 if (i == LEVELINFO_TOKEN_AUTHOR)
4097 fprintf(file, "\n");
4100 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4104 SetFilePermissions(filename, PERMS_PRIVATE);
4106 freeTreeInfo(level_info);
4110 char *getSetupValue(int type, void *value)
4112 static char value_string[MAX_LINE_LEN];
4120 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4124 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4128 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4129 *(int *)value == FALSE ? "off" : "on"));
4133 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4136 case TYPE_YES_NO_AUTO:
4137 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4138 *(int *)value == FALSE ? "no" : "yes"));
4142 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4146 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4150 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4154 sprintf(value_string, "%d", *(int *)value);
4158 if (*(char **)value == NULL)
4161 strcpy(value_string, *(char **)value);
4165 value_string[0] = '\0';
4169 if (type & TYPE_GHOSTED)
4170 strcpy(value_string, "n/a");
4172 return value_string;
4175 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4179 static char token_string[MAX_LINE_LEN];
4180 int token_type = token_info[token_nr].type;
4181 void *setup_value = token_info[token_nr].value;
4182 char *token_text = token_info[token_nr].text;
4183 char *value_string = getSetupValue(token_type, setup_value);
4185 /* build complete token string */
4186 sprintf(token_string, "%s%s", prefix, token_text);
4188 /* build setup entry line */
4189 line = getFormattedSetupEntry(token_string, value_string);
4191 if (token_type == TYPE_KEY_X11)
4193 Key key = *(Key *)setup_value;
4194 char *keyname = getKeyNameFromKey(key);
4196 /* add comment, if useful */
4197 if (!strEqual(keyname, "(undefined)") &&
4198 !strEqual(keyname, "(unknown)"))
4200 /* add at least one whitespace */
4202 for (i = strlen(line); i < token_comment_position; i++)
4206 strcat(line, keyname);
4213 void LoadLevelSetup_LastSeries()
4215 /* ----------------------------------------------------------------------- */
4216 /* ~/.<program>/levelsetup.conf */
4217 /* ----------------------------------------------------------------------- */
4219 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4220 SetupFileHash *level_setup_hash = NULL;
4222 /* always start with reliable default values */
4223 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4225 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
4226 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4228 if (leveldir_current == NULL)
4229 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4232 if ((level_setup_hash = loadSetupFileHash(filename)))
4234 char *last_level_series =
4235 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4237 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4239 if (leveldir_current == NULL)
4240 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4242 checkSetupFileHashIdentifier(level_setup_hash, filename,
4243 getCookie("LEVELSETUP"));
4245 freeSetupFileHash(level_setup_hash);
4248 Error(ERR_WARN, "using default setup values");
4253 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4255 /* ----------------------------------------------------------------------- */
4256 /* ~/.<program>/levelsetup.conf */
4257 /* ----------------------------------------------------------------------- */
4259 // check if the current level directory structure is available at this point
4260 if (leveldir_current == NULL)
4263 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4264 char *level_subdir = leveldir_current->subdir;
4267 InitUserDataDirectory();
4269 if (!(file = fopen(filename, MODE_WRITE)))
4271 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4278 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4279 getCookie("LEVELSETUP")));
4281 if (deactivate_last_level_series)
4282 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4284 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4289 SetFilePermissions(filename, PERMS_PRIVATE);
4294 void SaveLevelSetup_LastSeries()
4296 SaveLevelSetup_LastSeries_Ext(FALSE);
4299 void SaveLevelSetup_LastSeries_Deactivate()
4301 SaveLevelSetup_LastSeries_Ext(TRUE);
4306 static void checkSeriesInfo()
4308 static char *level_directory = NULL;
4311 DirectoryEntry *dir_entry;
4314 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4316 level_directory = getPath2((leveldir_current->in_user_dir ?
4317 getUserLevelDir(NULL) :
4318 options.level_directory),
4319 leveldir_current->fullpath);
4321 if ((dir = openDirectory(level_directory)) == NULL)
4323 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4329 while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */
4331 if (strlen(dir_entry->basename) > 4 &&
4332 dir_entry->basename[3] == '.' &&
4333 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4335 char levelnum_str[4];
4338 strncpy(levelnum_str, dir_entry->basename, 3);
4339 levelnum_str[3] = '\0';
4341 levelnum_value = atoi(levelnum_str);
4343 if (levelnum_value < leveldir_current->first_level)
4345 Error(ERR_WARN, "additional level %d found", levelnum_value);
4346 leveldir_current->first_level = levelnum_value;
4348 else if (levelnum_value > leveldir_current->last_level)
4350 Error(ERR_WARN, "additional level %d found", levelnum_value);
4351 leveldir_current->last_level = levelnum_value;
4357 closeDirectory(dir);
4362 static void checkSeriesInfo()
4364 static char *level_directory = NULL;
4367 struct dirent *dir_entry;
4370 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4372 level_directory = getPath2((leveldir_current->in_user_dir ?
4373 getUserLevelDir(NULL) :
4374 options.level_directory),
4375 leveldir_current->fullpath);
4377 if ((dir = opendir(level_directory)) == NULL)
4379 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4385 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4387 if (strlen(dir_entry->d_name) > 4 &&
4388 dir_entry->d_name[3] == '.' &&
4389 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4391 char levelnum_str[4];
4394 strncpy(levelnum_str, dir_entry->d_name, 3);
4395 levelnum_str[3] = '\0';
4397 levelnum_value = atoi(levelnum_str);
4399 if (levelnum_value < leveldir_current->first_level)
4401 Error(ERR_WARN, "additional level %d found", levelnum_value);
4402 leveldir_current->first_level = levelnum_value;
4404 else if (levelnum_value > leveldir_current->last_level)
4406 Error(ERR_WARN, "additional level %d found", levelnum_value);
4407 leveldir_current->last_level = levelnum_value;
4418 void LoadLevelSetup_SeriesInfo()
4421 SetupFileHash *level_setup_hash = NULL;
4422 char *level_subdir = leveldir_current->subdir;
4425 /* always start with reliable default values */
4426 level_nr = leveldir_current->first_level;
4428 for (i = 0; i < MAX_LEVELS; i++)
4430 LevelStats_setPlayed(i, 0);
4431 LevelStats_setSolved(i, 0);
4434 checkSeriesInfo(leveldir_current);
4436 /* ----------------------------------------------------------------------- */
4437 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4438 /* ----------------------------------------------------------------------- */
4440 level_subdir = leveldir_current->subdir;
4442 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4444 if ((level_setup_hash = loadSetupFileHash(filename)))
4448 /* get last played level in this level set */
4450 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4454 level_nr = atoi(token_value);
4456 if (level_nr < leveldir_current->first_level)
4457 level_nr = leveldir_current->first_level;
4458 if (level_nr > leveldir_current->last_level)
4459 level_nr = leveldir_current->last_level;
4462 /* get handicap level in this level set */
4464 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4468 int level_nr = atoi(token_value);
4470 if (level_nr < leveldir_current->first_level)
4471 level_nr = leveldir_current->first_level;
4472 if (level_nr > leveldir_current->last_level + 1)
4473 level_nr = leveldir_current->last_level;
4475 if (leveldir_current->user_defined || !leveldir_current->handicap)
4476 level_nr = leveldir_current->last_level;
4478 leveldir_current->handicap_level = level_nr;
4481 /* get number of played and solved levels in this level set */
4483 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4485 char *token = HASH_ITERATION_TOKEN(itr);
4486 char *value = HASH_ITERATION_VALUE(itr);
4488 if (strlen(token) == 3 &&
4489 token[0] >= '0' && token[0] <= '9' &&
4490 token[1] >= '0' && token[1] <= '9' &&
4491 token[2] >= '0' && token[2] <= '9')
4493 int level_nr = atoi(token);
4496 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4498 value = strchr(value, ' ');
4501 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4504 END_HASH_ITERATION(hash, itr)
4506 checkSetupFileHashIdentifier(level_setup_hash, filename,
4507 getCookie("LEVELSETUP"));
4509 freeSetupFileHash(level_setup_hash);
4512 Error(ERR_WARN, "using default setup values");
4517 void SaveLevelSetup_SeriesInfo()
4520 char *level_subdir = leveldir_current->subdir;
4521 char *level_nr_str = int2str(level_nr, 0);
4522 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4526 /* ----------------------------------------------------------------------- */
4527 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4528 /* ----------------------------------------------------------------------- */
4530 InitLevelSetupDirectory(level_subdir);
4532 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4534 if (!(file = fopen(filename, MODE_WRITE)))
4536 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4541 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4542 getCookie("LEVELSETUP")));
4543 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4545 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4546 handicap_level_str));
4548 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4551 if (LevelStats_getPlayed(i) > 0 ||
4552 LevelStats_getSolved(i) > 0)
4557 sprintf(token, "%03d", i);
4558 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4560 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4566 SetFilePermissions(filename, PERMS_PRIVATE);
4571 int LevelStats_getPlayed(int nr)
4573 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4576 int LevelStats_getSolved(int nr)
4578 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4581 void LevelStats_setPlayed(int nr, int value)
4583 if (nr >= 0 && nr < MAX_LEVELS)
4584 level_stats[nr].played = value;
4587 void LevelStats_setSolved(int nr, int value)
4589 if (nr >= 0 && nr < MAX_LEVELS)
4590 level_stats[nr].solved = value;
4593 void LevelStats_incPlayed(int nr)
4595 if (nr >= 0 && nr < MAX_LEVELS)
4596 level_stats[nr].played++;
4599 void LevelStats_incSolved(int nr)
4601 if (nr >= 0 && nr < MAX_LEVELS)
4602 level_stats[nr].solved++;