1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
23 #if !defined(PLATFORM_WIN32)
25 #include <sys/param.h>
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 char *data_dir = getCommonDataDir();
137 char *score_subdir = SCORES_DIRECTORY;
139 checked_free(score_dir);
141 if (level_subdir != NULL)
142 score_dir = getPath3(data_dir, score_subdir, level_subdir);
144 score_dir = getPath2(data_dir, score_subdir);
149 static char *getLevelSetupDir(char *level_subdir)
151 static char *levelsetup_dir = NULL;
152 char *data_dir = getUserGameDataDir();
153 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
155 checked_free(levelsetup_dir);
157 if (level_subdir != NULL)
158 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
160 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
162 return levelsetup_dir;
165 static char *getCacheDir()
167 static char *cache_dir = NULL;
169 if (cache_dir == NULL)
170 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
175 static char *getLevelDirFromTreeInfo(TreeInfo *node)
177 static char *level_dir = NULL;
180 return options.level_directory;
182 checked_free(level_dir);
184 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
185 options.level_directory), node->fullpath);
190 char *getCurrentLevelDir()
192 return getLevelDirFromTreeInfo(leveldir_current);
195 static char *getTapeDir(char *level_subdir)
197 static char *tape_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *tape_subdir = TAPES_DIRECTORY;
201 checked_free(tape_dir);
203 if (level_subdir != NULL)
204 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
206 tape_dir = getPath2(data_dir, tape_subdir);
211 static char *getSolutionTapeDir()
213 static char *tape_dir = NULL;
214 char *data_dir = getCurrentLevelDir();
215 char *tape_subdir = TAPES_DIRECTORY;
217 checked_free(tape_dir);
219 tape_dir = getPath2(data_dir, tape_subdir);
224 static char *getDefaultGraphicsDir(char *graphics_subdir)
226 static char *graphics_dir = NULL;
228 if (graphics_subdir == NULL)
229 return options.graphics_directory;
231 checked_free(graphics_dir);
233 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
238 static char *getDefaultSoundsDir(char *sounds_subdir)
240 static char *sounds_dir = NULL;
242 if (sounds_subdir == NULL)
243 return options.sounds_directory;
245 checked_free(sounds_dir);
247 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
252 static char *getDefaultMusicDir(char *music_subdir)
254 static char *music_dir = NULL;
256 if (music_subdir == NULL)
257 return options.music_directory;
259 checked_free(music_dir);
261 music_dir = getPath2(options.music_directory, music_subdir);
266 static char *getClassicArtworkSet(int type)
268 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
269 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
270 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
273 static char *getClassicArtworkDir(int type)
275 return (type == TREE_TYPE_GRAPHICS_DIR ?
276 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_SOUNDS_DIR ?
278 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
279 type == TREE_TYPE_MUSIC_DIR ?
280 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
283 static char *getUserGraphicsDir()
285 static char *usergraphics_dir = NULL;
287 if (usergraphics_dir == NULL)
288 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
290 return usergraphics_dir;
293 static char *getUserSoundsDir()
295 static char *usersounds_dir = NULL;
297 if (usersounds_dir == NULL)
298 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
300 return usersounds_dir;
303 static char *getUserMusicDir()
305 static char *usermusic_dir = NULL;
307 if (usermusic_dir == NULL)
308 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
310 return usermusic_dir;
313 static char *getSetupArtworkDir(TreeInfo *ti)
315 static char *artwork_dir = NULL;
317 checked_free(artwork_dir);
319 artwork_dir = getPath2(ti->basepath, ti->fullpath);
324 char *setLevelArtworkDir(TreeInfo *ti)
326 char **artwork_path_ptr, **artwork_set_ptr;
327 TreeInfo *level_artwork;
329 if (ti == NULL || leveldir_current == NULL)
332 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
333 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
335 checked_free(*artwork_path_ptr);
337 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
339 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
344 No (or non-existing) artwork configured in "levelinfo.conf". This would
345 normally result in using the artwork configured in the setup menu. But
346 if an artwork subdirectory exists (which might contain custom artwork
347 or an artwork configuration file), this level artwork must be treated
348 as relative to the default "classic" artwork, not to the artwork that
349 is currently configured in the setup menu.
351 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
352 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
353 the real "classic" artwork from the original R'n'D (like "gfx_classic").
356 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
358 checked_free(*artwork_set_ptr);
360 if (directoryExists(dir))
362 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
363 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
367 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
368 *artwork_set_ptr = NULL;
374 return *artwork_set_ptr;
377 inline static char *getLevelArtworkSet(int type)
379 if (leveldir_current == NULL)
382 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
385 inline static char *getLevelArtworkDir(int type)
387 if (leveldir_current == NULL)
388 return UNDEFINED_FILENAME;
390 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
393 char *getTapeFilename(int nr)
395 static char *filename = NULL;
396 char basename[MAX_FILENAME_LEN];
398 checked_free(filename);
400 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
401 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
406 char *getSolutionTapeFilename(int nr)
408 static char *filename = NULL;
409 char basename[MAX_FILENAME_LEN];
411 checked_free(filename);
413 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
414 filename = getPath2(getSolutionTapeDir(), basename);
416 if (!fileExists(filename))
418 static char *filename_sln = NULL;
420 checked_free(filename_sln);
422 sprintf(basename, "%03d.sln", nr);
423 filename_sln = getPath2(getSolutionTapeDir(), basename);
425 if (fileExists(filename_sln))
432 char *getScoreFilename(int nr)
434 static char *filename = NULL;
435 char basename[MAX_FILENAME_LEN];
437 checked_free(filename);
439 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
440 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
445 char *getSetupFilename()
447 static char *filename = NULL;
449 checked_free(filename);
451 filename = getPath2(getSetupDir(), SETUP_FILENAME);
456 char *getEditorSetupFilename()
458 static char *filename = NULL;
460 checked_free(filename);
461 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
463 if (fileExists(filename))
466 checked_free(filename);
467 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
472 char *getHelpAnimFilename()
474 static char *filename = NULL;
476 checked_free(filename);
478 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
483 char *getHelpTextFilename()
485 static char *filename = NULL;
487 checked_free(filename);
489 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
494 char *getLevelSetInfoFilename()
496 static char *filename = NULL;
511 for (i = 0; basenames[i] != NULL; i++)
513 checked_free(filename);
514 filename = getPath2(getCurrentLevelDir(), basenames[i]);
516 if (fileExists(filename))
523 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
525 static char basename[32];
527 sprintf(basename, "%s_%d.txt",
528 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
533 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
535 static char *filename = NULL;
537 boolean skip_setup_artwork = FALSE;
539 checked_free(filename);
541 basename = getLevelSetTitleMessageBasename(nr, initial);
543 if (!gfx.override_level_graphics)
545 /* 1st try: look for special artwork in current level series directory */
546 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
547 if (fileExists(filename))
552 /* 2nd try: look for message file in current level set directory */
553 filename = getPath2(getCurrentLevelDir(), basename);
554 if (fileExists(filename))
559 /* check if there is special artwork configured in level series config */
560 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
562 /* 3rd try: look for special artwork configured in level series config */
563 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
564 if (fileExists(filename))
569 /* take missing artwork configured in level set config from default */
570 skip_setup_artwork = TRUE;
574 if (!skip_setup_artwork)
576 /* 4th try: look for special artwork in configured artwork directory */
577 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
578 if (fileExists(filename))
584 /* 5th try: look for default artwork in new default artwork directory */
585 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
586 if (fileExists(filename))
591 /* 6th try: look for default artwork in old default artwork directory */
592 filename = getPath2(options.graphics_directory, basename);
593 if (fileExists(filename))
596 return NULL; /* cannot find specified artwork file anywhere */
599 static char *getCorrectedArtworkBasename(char *basename)
601 char *basename_corrected = basename;
603 #if defined(PLATFORM_MSDOS)
604 if (program.filename_prefix != NULL)
606 int prefix_len = strlen(program.filename_prefix);
608 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
609 basename_corrected = &basename[prefix_len];
611 /* if corrected filename is still longer than standard MS-DOS filename
612 size (8 characters + 1 dot + 3 characters file extension), shorten
613 filename by writing file extension after 8th basename character */
614 if (strlen(basename_corrected) > 8 + 1 + 3)
616 static char *msdos_filename = NULL;
618 checked_free(msdos_filename);
620 msdos_filename = getStringCopy(basename_corrected);
621 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
623 basename_corrected = msdos_filename;
628 return basename_corrected;
631 char *getCustomImageFilename(char *basename)
633 static char *filename = NULL;
634 boolean skip_setup_artwork = FALSE;
636 checked_free(filename);
638 basename = getCorrectedArtworkBasename(basename);
640 if (!gfx.override_level_graphics)
642 /* 1st try: look for special artwork in current level series directory */
643 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
644 if (fileExists(filename))
649 /* check if there is special artwork configured in level series config */
650 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
652 /* 2nd try: look for special artwork configured in level series config */
653 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
654 if (fileExists(filename))
659 /* take missing artwork configured in level set config from default */
660 skip_setup_artwork = TRUE;
664 if (!skip_setup_artwork)
666 /* 3rd try: look for special artwork in configured artwork directory */
667 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
668 if (fileExists(filename))
674 /* 4th try: look for default artwork in new default artwork directory */
675 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
676 if (fileExists(filename))
681 /* 5th try: look for default artwork in old default artwork directory */
682 filename = getPath2(options.graphics_directory, basename);
683 if (fileExists(filename))
686 #if defined(CREATE_SPECIAL_EDITION)
690 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
692 /* 6th try: look for fallback artwork in old default artwork directory */
693 /* (needed to prevent errors when trying to access unused artwork files) */
694 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
695 if (fileExists(filename))
699 return NULL; /* cannot find specified artwork file anywhere */
702 char *getCustomSoundFilename(char *basename)
704 static char *filename = NULL;
705 boolean skip_setup_artwork = FALSE;
707 checked_free(filename);
709 basename = getCorrectedArtworkBasename(basename);
711 if (!gfx.override_level_sounds)
713 /* 1st try: look for special artwork in current level series directory */
714 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
715 if (fileExists(filename))
720 /* check if there is special artwork configured in level series config */
721 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
723 /* 2nd try: look for special artwork configured in level series config */
724 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
725 if (fileExists(filename))
730 /* take missing artwork configured in level set config from default */
731 skip_setup_artwork = TRUE;
735 if (!skip_setup_artwork)
737 /* 3rd try: look for special artwork in configured artwork directory */
738 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
739 if (fileExists(filename))
745 /* 4th try: look for default artwork in new default artwork directory */
746 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
747 if (fileExists(filename))
752 /* 5th try: look for default artwork in old default artwork directory */
753 filename = getPath2(options.sounds_directory, basename);
754 if (fileExists(filename))
757 #if defined(CREATE_SPECIAL_EDITION)
761 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
763 /* 6th try: look for fallback artwork in old default artwork directory */
764 /* (needed to prevent errors when trying to access unused artwork files) */
765 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
766 if (fileExists(filename))
770 return NULL; /* cannot find specified artwork file anywhere */
773 char *getCustomMusicFilename(char *basename)
775 static char *filename = NULL;
776 boolean skip_setup_artwork = FALSE;
778 checked_free(filename);
780 basename = getCorrectedArtworkBasename(basename);
782 if (!gfx.override_level_music)
784 /* 1st try: look for special artwork in current level series directory */
785 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
786 if (fileExists(filename))
791 /* check if there is special artwork configured in level series config */
792 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
794 /* 2nd try: look for special artwork configured in level series config */
795 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
796 if (fileExists(filename))
801 /* take missing artwork configured in level set config from default */
802 skip_setup_artwork = TRUE;
806 if (!skip_setup_artwork)
808 /* 3rd try: look for special artwork in configured artwork directory */
809 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
810 if (fileExists(filename))
816 /* 4th try: look for default artwork in new default artwork directory */
817 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
818 if (fileExists(filename))
823 /* 5th try: look for default artwork in old default artwork directory */
824 filename = getPath2(options.music_directory, basename);
825 if (fileExists(filename))
828 #if defined(CREATE_SPECIAL_EDITION)
832 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
834 /* 6th try: look for fallback artwork in old default artwork directory */
835 /* (needed to prevent errors when trying to access unused artwork files) */
836 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
837 if (fileExists(filename))
841 return NULL; /* cannot find specified artwork file anywhere */
844 char *getCustomArtworkFilename(char *basename, int type)
846 if (type == ARTWORK_TYPE_GRAPHICS)
847 return getCustomImageFilename(basename);
848 else if (type == ARTWORK_TYPE_SOUNDS)
849 return getCustomSoundFilename(basename);
850 else if (type == ARTWORK_TYPE_MUSIC)
851 return getCustomMusicFilename(basename);
853 return UNDEFINED_FILENAME;
856 char *getCustomArtworkConfigFilename(int type)
858 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
861 char *getCustomArtworkLevelConfigFilename(int type)
863 static char *filename = NULL;
865 checked_free(filename);
867 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
872 char *getCustomMusicDirectory(void)
874 static char *directory = NULL;
875 boolean skip_setup_artwork = FALSE;
877 checked_free(directory);
879 if (!gfx.override_level_music)
881 /* 1st try: look for special artwork in current level series directory */
882 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
883 if (directoryExists(directory))
888 /* check if there is special artwork configured in level series config */
889 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
891 /* 2nd try: look for special artwork configured in level series config */
892 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
893 if (directoryExists(directory))
898 /* take missing artwork configured in level set config from default */
899 skip_setup_artwork = TRUE;
903 if (!skip_setup_artwork)
905 /* 3rd try: look for special artwork in configured artwork directory */
906 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
907 if (directoryExists(directory))
913 /* 4th try: look for default artwork in new default artwork directory */
914 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
915 if (directoryExists(directory))
920 /* 5th try: look for default artwork in old default artwork directory */
921 directory = getStringCopy(options.music_directory);
922 if (directoryExists(directory))
925 return NULL; /* cannot find specified artwork file anywhere */
928 void InitTapeDirectory(char *level_subdir)
930 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
931 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
932 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
935 void InitScoreDirectory(char *level_subdir)
937 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
938 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
939 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
942 static void SaveUserLevelInfo();
944 void InitUserLevelDirectory(char *level_subdir)
946 if (!directoryExists(getUserLevelDir(level_subdir)))
948 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
949 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
950 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
956 void InitLevelSetupDirectory(char *level_subdir)
958 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
959 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
960 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
963 void InitCacheDirectory()
965 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
966 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
970 /* ------------------------------------------------------------------------- */
971 /* some functions to handle lists of level and artwork directories */
972 /* ------------------------------------------------------------------------- */
974 TreeInfo *newTreeInfo()
976 return checked_calloc(sizeof(TreeInfo));
979 TreeInfo *newTreeInfo_setDefaults(int type)
981 TreeInfo *ti = newTreeInfo();
983 setTreeInfoToDefaults(ti, type);
988 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
990 node_new->next = *node_first;
991 *node_first = node_new;
994 int numTreeInfo(TreeInfo *node)
1007 boolean validLevelSeries(TreeInfo *node)
1009 return (node != NULL && !node->node_group && !node->parent_link);
1012 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1017 if (node->node_group) /* enter level group (step down into tree) */
1018 return getFirstValidTreeInfoEntry(node->node_group);
1019 else if (node->parent_link) /* skip start entry of level group */
1021 if (node->next) /* get first real level series entry */
1022 return getFirstValidTreeInfoEntry(node->next);
1023 else /* leave empty level group and go on */
1024 return getFirstValidTreeInfoEntry(node->node_parent->next);
1026 else /* this seems to be a regular level series */
1030 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1035 if (node->node_parent == NULL) /* top level group */
1036 return *node->node_top;
1037 else /* sub level group */
1038 return node->node_parent->node_group;
1041 int numTreeInfoInGroup(TreeInfo *node)
1043 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1046 int posTreeInfo(TreeInfo *node)
1048 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1053 if (node_cmp == node)
1057 node_cmp = node_cmp->next;
1063 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1065 TreeInfo *node_default = node;
1077 return node_default;
1080 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1082 if (identifier == NULL)
1087 if (node->node_group)
1089 TreeInfo *node_group;
1091 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1096 else if (!node->parent_link)
1098 if (strEqual(identifier, node->identifier))
1108 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1109 TreeInfo *node, boolean skip_sets_without_levels)
1116 if (!node->parent_link && !node->level_group &&
1117 skip_sets_without_levels && node->levels == 0)
1118 return cloneTreeNode(node_top, node_parent, node->next,
1119 skip_sets_without_levels);
1122 node_new = getTreeInfoCopy(node); /* copy complete node */
1124 node_new = newTreeInfo();
1126 *node_new = *node; /* copy complete node */
1129 node_new->node_top = node_top; /* correct top node link */
1130 node_new->node_parent = node_parent; /* correct parent node link */
1132 if (node->level_group)
1133 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1134 skip_sets_without_levels);
1136 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1137 skip_sets_without_levels);
1142 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1144 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1146 *ti_new = ti_cloned;
1149 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1151 boolean settings_changed = FALSE;
1155 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1156 !strEqual(node->graphics_set, node->graphics_set_ecs))
1158 setString(&node->graphics_set, node->graphics_set_ecs);
1159 settings_changed = TRUE;
1161 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1162 !strEqual(node->graphics_set, node->graphics_set_aga))
1164 setString(&node->graphics_set, node->graphics_set_aga);
1165 settings_changed = TRUE;
1168 if (node->node_group != NULL)
1169 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1174 return settings_changed;
1177 void dumpTreeInfo(TreeInfo *node, int depth)
1181 printf("Dumping TreeInfo:\n");
1185 for (i = 0; i < (depth + 1) * 3; i++)
1188 printf("'%s' / '%s'\n", node->identifier, node->name);
1191 // use for dumping artwork info tree
1192 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1193 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1196 if (node->node_group != NULL)
1197 dumpTreeInfo(node->node_group, depth + 1);
1203 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1204 int (*compare_function)(const void *,
1207 int num_nodes = numTreeInfo(*node_first);
1208 TreeInfo **sort_array;
1209 TreeInfo *node = *node_first;
1215 /* allocate array for sorting structure pointers */
1216 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1218 /* writing structure pointers to sorting array */
1219 while (i < num_nodes && node) /* double boundary check... */
1221 sort_array[i] = node;
1227 /* sorting the structure pointers in the sorting array */
1228 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1231 /* update the linkage of list elements with the sorted node array */
1232 for (i = 0; i < num_nodes - 1; i++)
1233 sort_array[i]->next = sort_array[i + 1];
1234 sort_array[num_nodes - 1]->next = NULL;
1236 /* update the linkage of the main list anchor pointer */
1237 *node_first = sort_array[0];
1241 /* now recursively sort the level group structures */
1245 if (node->node_group != NULL)
1246 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1252 void sortTreeInfo(TreeInfo **node_first)
1254 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1258 /* ========================================================================= */
1259 /* some stuff from "files.c" */
1260 /* ========================================================================= */
1262 #if defined(PLATFORM_WIN32)
1264 #define S_IRGRP S_IRUSR
1267 #define S_IROTH S_IRUSR
1270 #define S_IWGRP S_IWUSR
1273 #define S_IWOTH S_IWUSR
1276 #define S_IXGRP S_IXUSR
1279 #define S_IXOTH S_IXUSR
1282 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1287 #endif /* PLATFORM_WIN32 */
1289 /* file permissions for newly written files */
1290 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1291 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1292 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1294 #define MODE_W_PRIVATE (S_IWUSR)
1295 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1296 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1298 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1299 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1301 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1302 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1306 static char *dir = NULL;
1308 #if defined(PLATFORM_WIN32)
1311 dir = checked_malloc(MAX_PATH + 1);
1313 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1316 #elif defined(PLATFORM_UNIX)
1319 if ((dir = getenv("HOME")) == NULL)
1323 if ((pwd = getpwuid(getuid())) != NULL)
1324 dir = getStringCopy(pwd->pw_dir);
1336 char *getCommonDataDir(void)
1338 static char *common_data_dir = NULL;
1340 #if defined(PLATFORM_WIN32)
1341 if (common_data_dir == NULL)
1343 char *dir = checked_malloc(MAX_PATH + 1);
1345 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1346 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1347 common_data_dir = getPath2(dir, program.userdata_subdir);
1349 common_data_dir = options.rw_base_directory;
1352 if (common_data_dir == NULL)
1353 common_data_dir = options.rw_base_directory;
1356 return common_data_dir;
1359 char *getPersonalDataDir(void)
1361 static char *personal_data_dir = NULL;
1363 #if defined(PLATFORM_MACOSX)
1364 if (personal_data_dir == NULL)
1365 personal_data_dir = getPath2(getHomeDir(), "Documents");
1367 if (personal_data_dir == NULL)
1368 personal_data_dir = getHomeDir();
1371 return personal_data_dir;
1374 char *getUserGameDataDir(void)
1376 static char *user_game_data_dir = NULL;
1378 if (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 (directoryExists(userdata_dir_old) && !directoryExists(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 (!directoryExists(dir))
1460 if (posix_mkdir(dir, dir_mode) != 0)
1461 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1462 text, dir, strerror(errno));
1464 if (permission_class == PERMS_PUBLIC && !running_setgid)
1465 chmod(dir, dir_mode);
1467 posix_umask(last_umask); /* restore previous umask */
1470 void InitUserDataDirectory()
1472 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1475 void SetFilePermissions(char *filename, int permission_class)
1477 int running_setgid = posix_process_running_setgid();
1478 int perms = (permission_class == PERMS_PRIVATE ?
1479 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1481 if (permission_class == PERMS_PUBLIC && !running_setgid)
1482 perms |= MODE_W_ALL;
1484 chmod(filename, perms);
1487 char *getCookie(char *file_type)
1489 static char cookie[MAX_COOKIE_LEN + 1];
1491 if (strlen(program.cookie_prefix) + 1 +
1492 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1493 return "[COOKIE ERROR]"; /* should never happen */
1495 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1496 program.cookie_prefix, file_type,
1497 program.version_major, program.version_minor);
1502 int getFileVersionFromCookieString(const char *cookie)
1504 const char *ptr_cookie1, *ptr_cookie2;
1505 const char *pattern1 = "_FILE_VERSION_";
1506 const char *pattern2 = "?.?";
1507 const int len_cookie = strlen(cookie);
1508 const int len_pattern1 = strlen(pattern1);
1509 const int len_pattern2 = strlen(pattern2);
1510 const int len_pattern = len_pattern1 + len_pattern2;
1511 int version_major, version_minor;
1513 if (len_cookie <= len_pattern)
1516 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1517 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1519 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1522 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1523 ptr_cookie2[1] != '.' ||
1524 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1527 version_major = ptr_cookie2[0] - '0';
1528 version_minor = ptr_cookie2[2] - '0';
1530 return VERSION_IDENT(version_major, version_minor, 0, 0);
1533 boolean checkCookieString(const char *cookie, const char *template)
1535 const char *pattern = "_FILE_VERSION_?.?";
1536 const int len_cookie = strlen(cookie);
1537 const int len_template = strlen(template);
1538 const int len_pattern = strlen(pattern);
1540 if (len_cookie != len_template)
1543 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1549 /* ------------------------------------------------------------------------- */
1550 /* setup file list and hash handling functions */
1551 /* ------------------------------------------------------------------------- */
1553 char *getFormattedSetupEntry(char *token, char *value)
1556 static char entry[MAX_LINE_LEN];
1558 /* if value is an empty string, just return token without value */
1562 /* start with the token and some spaces to format output line */
1563 sprintf(entry, "%s:", token);
1564 for (i = strlen(entry); i < token_value_position; i++)
1567 /* continue with the token's value */
1568 strcat(entry, value);
1573 SetupFileList *newSetupFileList(char *token, char *value)
1575 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1577 new->token = getStringCopy(token);
1578 new->value = getStringCopy(value);
1585 void freeSetupFileList(SetupFileList *list)
1590 checked_free(list->token);
1591 checked_free(list->value);
1594 freeSetupFileList(list->next);
1599 char *getListEntry(SetupFileList *list, char *token)
1604 if (strEqual(list->token, token))
1607 return getListEntry(list->next, token);
1610 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1615 if (strEqual(list->token, token))
1617 checked_free(list->value);
1619 list->value = getStringCopy(value);
1623 else if (list->next == NULL)
1624 return (list->next = newSetupFileList(token, value));
1626 return setListEntry(list->next, token, value);
1629 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1634 if (list->next == NULL)
1635 return (list->next = newSetupFileList(token, value));
1637 return addListEntry(list->next, token, value);
1642 static void printSetupFileList(SetupFileList *list)
1647 printf("token: '%s'\n", list->token);
1648 printf("value: '%s'\n", list->value);
1650 printSetupFileList(list->next);
1656 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1657 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1658 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1659 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1661 #define insert_hash_entry hashtable_insert
1662 #define search_hash_entry hashtable_search
1663 #define change_hash_entry hashtable_change
1664 #define remove_hash_entry hashtable_remove
1667 unsigned int get_hash_from_key(void *key)
1672 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1673 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1674 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1675 it works better than many other constants, prime or not) has never been
1676 adequately explained.
1678 If you just want to have a good hash function, and cannot wait, djb2
1679 is one of the best string hash functions i know. It has excellent
1680 distribution and speed on many different sets of keys and table sizes.
1681 You are not likely to do better with one of the "well known" functions
1682 such as PJW, K&R, etc.
1684 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1687 char *str = (char *)key;
1688 unsigned int hash = 5381;
1691 while ((c = *str++))
1692 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1697 static int keys_are_equal(void *key1, void *key2)
1699 return (strEqual((char *)key1, (char *)key2));
1702 SetupFileHash *newSetupFileHash()
1704 SetupFileHash *new_hash =
1705 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1707 if (new_hash == NULL)
1708 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1713 void freeSetupFileHash(SetupFileHash *hash)
1718 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1721 char *getHashEntry(SetupFileHash *hash, char *token)
1726 return search_hash_entry(hash, token);
1729 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1736 value_copy = getStringCopy(value);
1738 /* change value; if it does not exist, insert it as new */
1739 if (!change_hash_entry(hash, token, value_copy))
1740 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1741 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1744 char *removeHashEntry(SetupFileHash *hash, char *token)
1749 return remove_hash_entry(hash, token);
1753 static void printSetupFileHash(SetupFileHash *hash)
1755 BEGIN_HASH_ITERATION(hash, itr)
1757 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1758 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1760 END_HASH_ITERATION(hash, itr)
1764 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1765 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1766 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1768 static boolean token_value_separator_found = FALSE;
1769 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1770 static boolean token_value_separator_warning = FALSE;
1772 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1773 static boolean token_already_exists_warning = FALSE;
1776 static boolean getTokenValueFromSetupLineExt(char *line,
1777 char **token_ptr, char **value_ptr,
1778 char *filename, char *line_raw,
1780 boolean separator_required)
1782 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1783 char *token, *value, *line_ptr;
1785 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1786 if (line_raw == NULL)
1788 strncpy(line_copy, line, MAX_LINE_LEN);
1789 line_copy[MAX_LINE_LEN] = '\0';
1792 strcpy(line_raw_copy, line_copy);
1793 line_raw = line_raw_copy;
1796 /* cut trailing comment from input line */
1797 for (line_ptr = line; *line_ptr; line_ptr++)
1799 if (*line_ptr == '#')
1806 /* cut trailing whitespaces from input line */
1807 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1808 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1811 /* ignore empty lines */
1815 /* cut leading whitespaces from token */
1816 for (token = line; *token; token++)
1817 if (*token != ' ' && *token != '\t')
1820 /* start with empty value as reliable default */
1823 token_value_separator_found = FALSE;
1825 /* find end of token to determine start of value */
1826 for (line_ptr = token; *line_ptr; line_ptr++)
1829 /* first look for an explicit token/value separator, like ':' or '=' */
1830 if (*line_ptr == ':' || *line_ptr == '=')
1832 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1835 *line_ptr = '\0'; /* terminate token string */
1836 value = line_ptr + 1; /* set beginning of value */
1838 token_value_separator_found = TRUE;
1844 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1845 /* fallback: if no token/value separator found, also allow whitespaces */
1846 if (!token_value_separator_found && !separator_required)
1848 for (line_ptr = token; *line_ptr; line_ptr++)
1850 if (*line_ptr == ' ' || *line_ptr == '\t')
1852 *line_ptr = '\0'; /* terminate token string */
1853 value = line_ptr + 1; /* set beginning of value */
1855 token_value_separator_found = TRUE;
1861 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1862 if (token_value_separator_found)
1864 if (!token_value_separator_warning)
1866 Error(ERR_INFO_LINE, "-");
1868 if (filename != NULL)
1870 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1871 Error(ERR_INFO, "- config file: '%s'", filename);
1875 Error(ERR_WARN, "missing token/value separator(s):");
1878 token_value_separator_warning = TRUE;
1881 if (filename != NULL)
1882 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1884 Error(ERR_INFO, "- line: '%s'", line_raw);
1890 /* cut trailing whitespaces from token */
1891 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1892 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1895 /* cut leading whitespaces from value */
1896 for (; *value; value++)
1897 if (*value != ' ' && *value != '\t')
1902 value = "true"; /* treat tokens without value as "true" */
1911 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1913 /* while the internal (old) interface does not require a token/value
1914 separator (for downwards compatibility with existing files which
1915 don't use them), it is mandatory for the external (new) interface */
1917 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1923 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1924 boolean top_recursion_level, boolean is_hash)
1926 static SetupFileHash *include_filename_hash = NULL;
1927 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1928 char *token, *value, *line_ptr;
1929 void *insert_ptr = NULL;
1930 boolean read_continued_line = FALSE;
1932 int line_nr = 0, token_count = 0, include_count = 0;
1934 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1935 token_value_separator_warning = FALSE;
1938 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1939 token_already_exists_warning = FALSE;
1943 Error(ERR_INFO, "===== opening file: '%s'", filename);
1946 if (!(file = openFile(filename, MODE_READ)))
1948 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1954 Error(ERR_INFO, "===== reading file: '%s'", filename);
1957 /* use "insert pointer" to store list end for constant insertion complexity */
1959 insert_ptr = setup_file_data;
1961 /* on top invocation, create hash to mark included files (to prevent loops) */
1962 if (top_recursion_level)
1963 include_filename_hash = newSetupFileHash();
1965 /* mark this file as already included (to prevent including it again) */
1966 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1968 while (!checkEndOfFile(file))
1970 /* read next line of input file */
1971 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1975 Error(ERR_INFO, "got line: '%s'", line);
1978 /* check if line was completely read and is terminated by line break */
1979 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1982 /* cut trailing line break (this can be newline and/or carriage return) */
1983 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1984 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1987 /* copy raw input line for later use (mainly debugging output) */
1988 strcpy(line_raw, line);
1990 if (read_continued_line)
1993 /* !!! ??? WHY ??? !!! */
1994 /* cut leading whitespaces from input line */
1995 for (line_ptr = line; *line_ptr; line_ptr++)
1996 if (*line_ptr != ' ' && *line_ptr != '\t')
2000 /* append new line to existing line, if there is enough space */
2001 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2002 strcat(previous_line, line_ptr);
2004 strcpy(line, previous_line); /* copy storage buffer to line */
2006 read_continued_line = FALSE;
2009 /* if the last character is '\', continue at next line */
2010 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2012 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2013 strcpy(previous_line, line); /* copy line to storage buffer */
2015 read_continued_line = TRUE;
2020 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2021 line_raw, line_nr, FALSE))
2026 if (strEqual(token, "include"))
2028 if (getHashEntry(include_filename_hash, value) == NULL)
2030 char *basepath = getBasePath(filename);
2031 char *basename = getBaseName(value);
2032 char *filename_include = getPath2(basepath, basename);
2035 Error(ERR_INFO, "[including file '%s']", filename_include);
2038 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2042 free(filename_include);
2048 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2055 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2057 getHashEntry((SetupFileHash *)setup_file_data, token);
2059 if (old_value != NULL)
2061 if (!token_already_exists_warning)
2063 Error(ERR_INFO_LINE, "-");
2064 Error(ERR_WARN, "duplicate token(s) found in config file:");
2065 Error(ERR_INFO, "- config file: '%s'", filename);
2067 token_already_exists_warning = TRUE;
2070 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2071 Error(ERR_INFO, " old value: '%s'", old_value);
2072 Error(ERR_INFO, " new value: '%s'", value);
2076 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2080 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2090 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2091 if (token_value_separator_warning)
2092 Error(ERR_INFO_LINE, "-");
2095 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2096 if (token_already_exists_warning)
2097 Error(ERR_INFO_LINE, "-");
2100 if (token_count == 0 && include_count == 0)
2101 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2103 if (top_recursion_level)
2104 freeSetupFileHash(include_filename_hash);
2111 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2112 boolean top_recursion_level, boolean is_hash)
2114 static SetupFileHash *include_filename_hash = NULL;
2115 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2116 char *token, *value, *line_ptr;
2117 void *insert_ptr = NULL;
2118 boolean read_continued_line = FALSE;
2120 int line_nr = 0, token_count = 0, include_count = 0;
2122 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2123 token_value_separator_warning = FALSE;
2126 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2127 token_already_exists_warning = FALSE;
2130 if (!(file = fopen(filename, MODE_READ)))
2132 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2137 /* use "insert pointer" to store list end for constant insertion complexity */
2139 insert_ptr = setup_file_data;
2141 /* on top invocation, create hash to mark included files (to prevent loops) */
2142 if (top_recursion_level)
2143 include_filename_hash = newSetupFileHash();
2145 /* mark this file as already included (to prevent including it again) */
2146 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2150 /* read next line of input file */
2151 if (!fgets(line, MAX_LINE_LEN, file))
2154 /* check if line was completely read and is terminated by line break */
2155 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2158 /* cut trailing line break (this can be newline and/or carriage return) */
2159 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2160 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2163 /* copy raw input line for later use (mainly debugging output) */
2164 strcpy(line_raw, line);
2166 if (read_continued_line)
2169 /* !!! ??? WHY ??? !!! */
2170 /* cut leading whitespaces from input line */
2171 for (line_ptr = line; *line_ptr; line_ptr++)
2172 if (*line_ptr != ' ' && *line_ptr != '\t')
2176 /* append new line to existing line, if there is enough space */
2177 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2178 strcat(previous_line, line_ptr);
2180 strcpy(line, previous_line); /* copy storage buffer to line */
2182 read_continued_line = FALSE;
2185 /* if the last character is '\', continue at next line */
2186 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2188 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2189 strcpy(previous_line, line); /* copy line to storage buffer */
2191 read_continued_line = TRUE;
2196 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2197 line_raw, line_nr, FALSE))
2202 if (strEqual(token, "include"))
2204 if (getHashEntry(include_filename_hash, value) == NULL)
2206 char *basepath = getBasePath(filename);
2207 char *basename = getBaseName(value);
2208 char *filename_include = getPath2(basepath, basename);
2211 Error(ERR_INFO, "[including file '%s']", filename_include);
2214 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2218 free(filename_include);
2224 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2231 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2233 getHashEntry((SetupFileHash *)setup_file_data, token);
2235 if (old_value != NULL)
2237 if (!token_already_exists_warning)
2239 Error(ERR_INFO_LINE, "-");
2240 Error(ERR_WARN, "duplicate token(s) found in config file:");
2241 Error(ERR_INFO, "- config file: '%s'", filename);
2243 token_already_exists_warning = TRUE;
2246 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2247 Error(ERR_INFO, " old value: '%s'", old_value);
2248 Error(ERR_INFO, " new value: '%s'", value);
2252 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2256 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2266 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2267 if (token_value_separator_warning)
2268 Error(ERR_INFO_LINE, "-");
2271 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2272 if (token_already_exists_warning)
2273 Error(ERR_INFO_LINE, "-");
2276 if (token_count == 0 && include_count == 0)
2277 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2279 if (top_recursion_level)
2280 freeSetupFileHash(include_filename_hash);
2289 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2290 boolean top_recursion_level, boolean is_hash)
2292 static SetupFileHash *include_filename_hash = NULL;
2293 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2294 char *token, *value, *line_ptr;
2295 void *insert_ptr = NULL;
2296 boolean read_continued_line = FALSE;
2299 int token_count = 0;
2301 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2302 token_value_separator_warning = FALSE;
2305 if (!(file = fopen(filename, MODE_READ)))
2307 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2312 /* use "insert pointer" to store list end for constant insertion complexity */
2314 insert_ptr = setup_file_data;
2316 /* on top invocation, create hash to mark included files (to prevent loops) */
2317 if (top_recursion_level)
2318 include_filename_hash = newSetupFileHash();
2320 /* mark this file as already included (to prevent including it again) */
2321 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2325 /* read next line of input file */
2326 if (!fgets(line, MAX_LINE_LEN, file))
2329 /* check if line was completely read and is terminated by line break */
2330 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2333 /* cut trailing line break (this can be newline and/or carriage return) */
2334 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2335 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2338 /* copy raw input line for later use (mainly debugging output) */
2339 strcpy(line_raw, line);
2341 if (read_continued_line)
2343 /* cut leading whitespaces from input line */
2344 for (line_ptr = line; *line_ptr; line_ptr++)
2345 if (*line_ptr != ' ' && *line_ptr != '\t')
2348 /* append new line to existing line, if there is enough space */
2349 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2350 strcat(previous_line, line_ptr);
2352 strcpy(line, previous_line); /* copy storage buffer to line */
2354 read_continued_line = FALSE;
2357 /* if the last character is '\', continue at next line */
2358 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2360 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2361 strcpy(previous_line, line); /* copy line to storage buffer */
2363 read_continued_line = TRUE;
2368 /* cut trailing comment from input line */
2369 for (line_ptr = line; *line_ptr; line_ptr++)
2371 if (*line_ptr == '#')
2378 /* cut trailing whitespaces from input line */
2379 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2380 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2383 /* ignore empty lines */
2387 /* cut leading whitespaces from token */
2388 for (token = line; *token; token++)
2389 if (*token != ' ' && *token != '\t')
2392 /* start with empty value as reliable default */
2395 token_value_separator_found = FALSE;
2397 /* find end of token to determine start of value */
2398 for (line_ptr = token; *line_ptr; line_ptr++)
2401 /* first look for an explicit token/value separator, like ':' or '=' */
2402 if (*line_ptr == ':' || *line_ptr == '=')
2404 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2407 *line_ptr = '\0'; /* terminate token string */
2408 value = line_ptr + 1; /* set beginning of value */
2410 token_value_separator_found = TRUE;
2416 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2417 /* fallback: if no token/value separator found, also allow whitespaces */
2418 if (!token_value_separator_found)
2420 for (line_ptr = token; *line_ptr; line_ptr++)
2422 if (*line_ptr == ' ' || *line_ptr == '\t')
2424 *line_ptr = '\0'; /* terminate token string */
2425 value = line_ptr + 1; /* set beginning of value */
2427 token_value_separator_found = TRUE;
2433 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2434 if (token_value_separator_found)
2436 if (!token_value_separator_warning)
2438 Error(ERR_INFO_LINE, "-");
2439 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2440 Error(ERR_INFO, "- config file: '%s'", filename);
2442 token_value_separator_warning = TRUE;
2445 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2451 /* cut trailing whitespaces from token */
2452 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2453 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2456 /* cut leading whitespaces from value */
2457 for (; *value; value++)
2458 if (*value != ' ' && *value != '\t')
2463 value = "true"; /* treat tokens without value as "true" */
2468 if (strEqual(token, "include"))
2470 if (getHashEntry(include_filename_hash, value) == NULL)
2472 char *basepath = getBasePath(filename);
2473 char *basename = getBaseName(value);
2474 char *filename_include = getPath2(basepath, basename);
2477 Error(ERR_INFO, "[including file '%s']", filename_include);
2480 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2484 free(filename_include);
2488 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2494 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2496 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2505 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2506 if (token_value_separator_warning)
2507 Error(ERR_INFO_LINE, "-");
2510 if (token_count == 0)
2511 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2513 if (top_recursion_level)
2514 freeSetupFileHash(include_filename_hash);
2520 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2524 if (!(file = fopen(filename, MODE_WRITE)))
2526 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2531 BEGIN_HASH_ITERATION(hash, itr)
2533 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2534 HASH_ITERATION_VALUE(itr)));
2536 END_HASH_ITERATION(hash, itr)
2541 SetupFileList *loadSetupFileList(char *filename)
2543 SetupFileList *setup_file_list = newSetupFileList("", "");
2544 SetupFileList *first_valid_list_entry;
2546 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2548 freeSetupFileList(setup_file_list);
2553 first_valid_list_entry = setup_file_list->next;
2555 /* free empty list header */
2556 setup_file_list->next = NULL;
2557 freeSetupFileList(setup_file_list);
2559 return first_valid_list_entry;
2562 SetupFileHash *loadSetupFileHash(char *filename)
2564 SetupFileHash *setup_file_hash = newSetupFileHash();
2566 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2568 freeSetupFileHash(setup_file_hash);
2573 return setup_file_hash;
2576 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2577 char *filename, char *identifier)
2579 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2582 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2583 else if (!checkCookieString(value, identifier))
2584 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2588 /* ========================================================================= */
2589 /* setup file stuff */
2590 /* ========================================================================= */
2592 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2593 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2594 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2596 /* level directory info */
2597 #define LEVELINFO_TOKEN_IDENTIFIER 0
2598 #define LEVELINFO_TOKEN_NAME 1
2599 #define LEVELINFO_TOKEN_NAME_SORTING 2
2600 #define LEVELINFO_TOKEN_AUTHOR 3
2601 #define LEVELINFO_TOKEN_YEAR 4
2602 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2603 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2604 #define LEVELINFO_TOKEN_TESTED_BY 7
2605 #define LEVELINFO_TOKEN_LEVELS 8
2606 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2607 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2608 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2609 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2610 #define LEVELINFO_TOKEN_READONLY 13
2611 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2612 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2613 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2614 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2615 #define LEVELINFO_TOKEN_MUSIC_SET 18
2616 #define LEVELINFO_TOKEN_FILENAME 19
2617 #define LEVELINFO_TOKEN_FILETYPE 20
2618 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2619 #define LEVELINFO_TOKEN_HANDICAP 22
2620 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2622 #define NUM_LEVELINFO_TOKENS 24
2624 static LevelDirTree ldi;
2626 static struct TokenInfo levelinfo_tokens[] =
2628 /* level directory info */
2629 { TYPE_STRING, &ldi.identifier, "identifier" },
2630 { TYPE_STRING, &ldi.name, "name" },
2631 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2632 { TYPE_STRING, &ldi.author, "author" },
2633 { TYPE_STRING, &ldi.year, "year" },
2634 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2635 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2636 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2637 { TYPE_INTEGER, &ldi.levels, "levels" },
2638 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2639 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2640 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2641 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2642 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2643 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2644 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2645 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2646 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2647 { TYPE_STRING, &ldi.music_set, "music_set" },
2648 { TYPE_STRING, &ldi.level_filename, "filename" },
2649 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2650 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2651 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2652 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2655 static struct TokenInfo artworkinfo_tokens[] =
2657 /* artwork directory info */
2658 { TYPE_STRING, &ldi.identifier, "identifier" },
2659 { TYPE_STRING, &ldi.subdir, "subdir" },
2660 { TYPE_STRING, &ldi.name, "name" },
2661 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2662 { TYPE_STRING, &ldi.author, "author" },
2663 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2664 { TYPE_STRING, &ldi.basepath, "basepath" },
2665 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2666 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2667 { TYPE_INTEGER, &ldi.color, "color" },
2668 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2673 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2677 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2678 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2679 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2680 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2683 ti->node_parent = NULL;
2684 ti->node_group = NULL;
2691 ti->fullpath = NULL;
2692 ti->basepath = NULL;
2693 ti->identifier = NULL;
2694 ti->name = getStringCopy(ANONYMOUS_NAME);
2695 ti->name_sorting = NULL;
2696 ti->author = getStringCopy(ANONYMOUS_NAME);
2699 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2700 ti->latest_engine = FALSE; /* default: get from level */
2701 ti->parent_link = FALSE;
2702 ti->in_user_dir = FALSE;
2703 ti->user_defined = FALSE;
2705 ti->class_desc = NULL;
2707 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2709 if (ti->type == TREE_TYPE_LEVEL_DIR)
2711 ti->imported_from = NULL;
2712 ti->imported_by = NULL;
2713 ti->tested_by = NULL;
2715 ti->graphics_set_ecs = NULL;
2716 ti->graphics_set_aga = NULL;
2717 ti->graphics_set = NULL;
2718 ti->sounds_set = NULL;
2719 ti->music_set = NULL;
2720 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2721 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2722 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2724 ti->level_filename = NULL;
2725 ti->level_filetype = NULL;
2727 ti->special_flags = NULL;
2730 ti->first_level = 0;
2732 ti->level_group = FALSE;
2733 ti->handicap_level = 0;
2734 ti->readonly = TRUE;
2735 ti->handicap = TRUE;
2736 ti->skip_levels = FALSE;
2740 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2744 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2746 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2751 /* copy all values from the parent structure */
2753 ti->type = parent->type;
2755 ti->node_top = parent->node_top;
2756 ti->node_parent = parent;
2757 ti->node_group = NULL;
2764 ti->fullpath = NULL;
2765 ti->basepath = NULL;
2766 ti->identifier = NULL;
2767 ti->name = getStringCopy(ANONYMOUS_NAME);
2768 ti->name_sorting = NULL;
2769 ti->author = getStringCopy(parent->author);
2770 ti->year = getStringCopy(parent->year);
2772 ti->sort_priority = parent->sort_priority;
2773 ti->latest_engine = parent->latest_engine;
2774 ti->parent_link = FALSE;
2775 ti->in_user_dir = parent->in_user_dir;
2776 ti->user_defined = parent->user_defined;
2777 ti->color = parent->color;
2778 ti->class_desc = getStringCopy(parent->class_desc);
2780 ti->infotext = getStringCopy(parent->infotext);
2782 if (ti->type == TREE_TYPE_LEVEL_DIR)
2784 ti->imported_from = getStringCopy(parent->imported_from);
2785 ti->imported_by = getStringCopy(parent->imported_by);
2786 ti->tested_by = getStringCopy(parent->tested_by);
2788 ti->graphics_set_ecs = NULL;
2789 ti->graphics_set_aga = NULL;
2790 ti->graphics_set = NULL;
2791 ti->sounds_set = NULL;
2792 ti->music_set = NULL;
2793 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2794 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2795 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2797 ti->level_filename = NULL;
2798 ti->level_filetype = NULL;
2800 ti->special_flags = getStringCopy(parent->special_flags);
2803 ti->first_level = 0;
2805 ti->level_group = FALSE;
2806 ti->handicap_level = 0;
2808 ti->readonly = parent->readonly;
2810 ti->readonly = TRUE;
2812 ti->handicap = TRUE;
2813 ti->skip_levels = FALSE;
2817 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2819 TreeInfo *ti_copy = newTreeInfo();
2821 /* copy all values from the original structure */
2823 ti_copy->type = ti->type;
2825 ti_copy->node_top = ti->node_top;
2826 ti_copy->node_parent = ti->node_parent;
2827 ti_copy->node_group = ti->node_group;
2828 ti_copy->next = ti->next;
2830 ti_copy->cl_first = ti->cl_first;
2831 ti_copy->cl_cursor = ti->cl_cursor;
2833 ti_copy->subdir = getStringCopy(ti->subdir);
2834 ti_copy->fullpath = getStringCopy(ti->fullpath);
2835 ti_copy->basepath = getStringCopy(ti->basepath);
2836 ti_copy->identifier = getStringCopy(ti->identifier);
2837 ti_copy->name = getStringCopy(ti->name);
2838 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2839 ti_copy->author = getStringCopy(ti->author);
2840 ti_copy->year = getStringCopy(ti->year);
2841 ti_copy->imported_from = getStringCopy(ti->imported_from);
2842 ti_copy->imported_by = getStringCopy(ti->imported_by);
2843 ti_copy->tested_by = getStringCopy(ti->tested_by);
2845 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2846 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2847 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2848 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2849 ti_copy->music_set = getStringCopy(ti->music_set);
2850 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2851 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2852 ti_copy->music_path = getStringCopy(ti->music_path);
2854 ti_copy->level_filename = getStringCopy(ti->level_filename);
2855 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2857 ti_copy->special_flags = getStringCopy(ti->special_flags);
2859 ti_copy->levels = ti->levels;
2860 ti_copy->first_level = ti->first_level;
2861 ti_copy->last_level = ti->last_level;
2862 ti_copy->sort_priority = ti->sort_priority;
2864 ti_copy->latest_engine = ti->latest_engine;
2866 ti_copy->level_group = ti->level_group;
2867 ti_copy->parent_link = ti->parent_link;
2868 ti_copy->in_user_dir = ti->in_user_dir;
2869 ti_copy->user_defined = ti->user_defined;
2870 ti_copy->readonly = ti->readonly;
2871 ti_copy->handicap = ti->handicap;
2872 ti_copy->skip_levels = ti->skip_levels;
2874 ti_copy->color = ti->color;
2875 ti_copy->class_desc = getStringCopy(ti->class_desc);
2876 ti_copy->handicap_level = ti->handicap_level;
2878 ti_copy->infotext = getStringCopy(ti->infotext);
2883 void freeTreeInfo(TreeInfo *ti)
2888 checked_free(ti->subdir);
2889 checked_free(ti->fullpath);
2890 checked_free(ti->basepath);
2891 checked_free(ti->identifier);
2893 checked_free(ti->name);
2894 checked_free(ti->name_sorting);
2895 checked_free(ti->author);
2896 checked_free(ti->year);
2898 checked_free(ti->class_desc);
2900 checked_free(ti->infotext);
2902 if (ti->type == TREE_TYPE_LEVEL_DIR)
2904 checked_free(ti->imported_from);
2905 checked_free(ti->imported_by);
2906 checked_free(ti->tested_by);
2908 checked_free(ti->graphics_set_ecs);
2909 checked_free(ti->graphics_set_aga);
2910 checked_free(ti->graphics_set);
2911 checked_free(ti->sounds_set);
2912 checked_free(ti->music_set);
2914 checked_free(ti->graphics_path);
2915 checked_free(ti->sounds_path);
2916 checked_free(ti->music_path);
2918 checked_free(ti->level_filename);
2919 checked_free(ti->level_filetype);
2921 checked_free(ti->special_flags);
2927 void setSetupInfo(struct TokenInfo *token_info,
2928 int token_nr, char *token_value)
2930 int token_type = token_info[token_nr].type;
2931 void *setup_value = token_info[token_nr].value;
2933 if (token_value == NULL)
2936 /* set setup field to corresponding token value */
2941 *(boolean *)setup_value = get_boolean_from_string(token_value);
2945 *(int *)setup_value = get_switch3_from_string(token_value);
2949 *(Key *)setup_value = getKeyFromKeyName(token_value);
2953 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2957 *(int *)setup_value = get_integer_from_string(token_value);
2961 checked_free(*(char **)setup_value);
2962 *(char **)setup_value = getStringCopy(token_value);
2970 static int compareTreeInfoEntries(const void *object1, const void *object2)
2972 const TreeInfo *entry1 = *((TreeInfo **)object1);
2973 const TreeInfo *entry2 = *((TreeInfo **)object2);
2974 int class_sorting1 = 0, class_sorting2 = 0;
2977 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2979 class_sorting1 = LEVELSORTING(entry1);
2980 class_sorting2 = LEVELSORTING(entry2);
2982 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2983 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2984 entry1->type == TREE_TYPE_MUSIC_DIR)
2986 class_sorting1 = ARTWORKSORTING(entry1);
2987 class_sorting2 = ARTWORKSORTING(entry2);
2990 if (entry1->parent_link || entry2->parent_link)
2991 compare_result = (entry1->parent_link ? -1 : +1);
2992 else if (entry1->sort_priority == entry2->sort_priority)
2994 char *name1 = getStringToLower(entry1->name_sorting);
2995 char *name2 = getStringToLower(entry2->name_sorting);
2997 compare_result = strcmp(name1, name2);
3002 else if (class_sorting1 == class_sorting2)
3003 compare_result = entry1->sort_priority - entry2->sort_priority;
3005 compare_result = class_sorting1 - class_sorting2;
3007 return compare_result;
3010 static void createParentTreeInfoNode(TreeInfo *node_parent)
3014 if (node_parent == NULL)
3017 ti_new = newTreeInfo();
3018 setTreeInfoToDefaults(ti_new, node_parent->type);
3020 ti_new->node_parent = node_parent;
3021 ti_new->parent_link = TRUE;
3023 setString(&ti_new->identifier, node_parent->identifier);
3024 setString(&ti_new->name, ".. (parent directory)");
3025 setString(&ti_new->name_sorting, ti_new->name);
3027 setString(&ti_new->subdir, "..");
3028 setString(&ti_new->fullpath, node_parent->fullpath);
3030 ti_new->sort_priority = node_parent->sort_priority;
3031 ti_new->latest_engine = node_parent->latest_engine;
3033 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3035 pushTreeInfo(&node_parent->node_group, ti_new);
3039 /* -------------------------------------------------------------------------- */
3040 /* functions for handling level and custom artwork info cache */
3041 /* -------------------------------------------------------------------------- */
3043 static void LoadArtworkInfoCache()
3045 InitCacheDirectory();
3047 if (artworkinfo_cache_old == NULL)
3049 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3051 /* try to load artwork info hash from already existing cache file */
3052 artworkinfo_cache_old = loadSetupFileHash(filename);
3054 /* if no artwork info cache file was found, start with empty hash */
3055 if (artworkinfo_cache_old == NULL)
3056 artworkinfo_cache_old = newSetupFileHash();
3061 if (artworkinfo_cache_new == NULL)
3062 artworkinfo_cache_new = newSetupFileHash();
3065 static void SaveArtworkInfoCache()
3067 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3069 InitCacheDirectory();
3071 saveSetupFileHash(artworkinfo_cache_new, filename);
3076 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3078 static char *prefix = NULL;
3080 checked_free(prefix);
3082 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3087 /* (identical to above function, but separate string buffer needed -- nasty) */
3088 static char *getCacheToken(char *prefix, char *suffix)
3090 static char *token = NULL;
3092 checked_free(token);
3094 token = getStringCat2WithSeparator(prefix, suffix, ".");
3099 static char *getFileTimestampString(char *filename)
3102 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3104 struct stat file_status;
3106 if (stat(filename, &file_status) != 0) /* cannot stat file */
3107 return getStringCopy(i_to_a(0));
3109 return getStringCopy(i_to_a(file_status.st_mtime));
3113 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3115 struct stat file_status;
3117 if (timestamp_string == NULL)
3120 if (stat(filename, &file_status) != 0) /* cannot stat file */
3123 return (file_status.st_mtime != atoi(timestamp_string));
3126 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3128 char *identifier = level_node->subdir;
3129 char *type_string = ARTWORK_DIRECTORY(type);
3130 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3131 char *token_main = getCacheToken(token_prefix, "CACHED");
3132 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3133 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3134 TreeInfo *artwork_info = NULL;
3136 if (!use_artworkinfo_cache)
3143 artwork_info = newTreeInfo();
3144 setTreeInfoToDefaults(artwork_info, type);
3146 /* set all structure fields according to the token/value pairs */
3147 ldi = *artwork_info;
3148 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3150 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3151 char *value = getHashEntry(artworkinfo_cache_old, token);
3153 setSetupInfo(artworkinfo_tokens, i, value);
3155 /* check if cache entry for this item is invalid or incomplete */
3159 Error(ERR_WARN, "cache entry '%s' invalid", token);
3166 *artwork_info = ldi;
3171 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3172 LEVELINFO_FILENAME);
3173 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3174 ARTWORKINFO_FILENAME(type));
3176 /* check if corresponding "levelinfo.conf" file has changed */
3177 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3178 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3180 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3183 /* check if corresponding "<artworkinfo>.conf" file has changed */
3184 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3185 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3187 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3192 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
3195 checked_free(filename_levelinfo);
3196 checked_free(filename_artworkinfo);
3199 if (!cached && artwork_info != NULL)
3201 freeTreeInfo(artwork_info);
3206 return artwork_info;
3209 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3210 LevelDirTree *level_node, int type)
3212 char *identifier = level_node->subdir;
3213 char *type_string = ARTWORK_DIRECTORY(type);
3214 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3215 char *token_main = getCacheToken(token_prefix, "CACHED");
3216 boolean set_cache_timestamps = TRUE;
3219 setHashEntry(artworkinfo_cache_new, token_main, "true");
3221 if (set_cache_timestamps)
3223 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3224 LEVELINFO_FILENAME);
3225 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3226 ARTWORKINFO_FILENAME(type));
3227 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3228 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3230 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3231 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3233 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3234 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3236 checked_free(filename_levelinfo);
3237 checked_free(filename_artworkinfo);
3238 checked_free(timestamp_levelinfo);
3239 checked_free(timestamp_artworkinfo);
3242 ldi = *artwork_info;
3243 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3245 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3246 char *value = getSetupValue(artworkinfo_tokens[i].type,
3247 artworkinfo_tokens[i].value);
3249 setHashEntry(artworkinfo_cache_new, token, value);
3254 /* -------------------------------------------------------------------------- */
3255 /* functions for loading level info and custom artwork info */
3256 /* -------------------------------------------------------------------------- */
3258 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3259 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3261 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3262 TreeInfo *node_parent,
3263 char *level_directory,
3264 char *directory_name)
3267 static unsigned int progress_delay = 0;
3268 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3270 char *directory_path = getPath2(level_directory, directory_name);
3271 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3272 SetupFileHash *setup_file_hash;
3273 LevelDirTree *leveldir_new = NULL;
3276 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3277 if (!options.debug && !fileExists(filename))
3279 free(directory_path);
3285 setup_file_hash = loadSetupFileHash(filename);
3287 if (setup_file_hash == NULL)
3289 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3291 free(directory_path);
3297 leveldir_new = newTreeInfo();
3300 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3302 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3304 leveldir_new->subdir = getStringCopy(directory_name);
3306 checkSetupFileHashIdentifier(setup_file_hash, filename,
3307 getCookie("LEVELINFO"));
3309 /* set all structure fields according to the token/value pairs */
3310 ldi = *leveldir_new;
3311 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3312 setSetupInfo(levelinfo_tokens, i,
3313 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3314 *leveldir_new = ldi;
3316 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3317 setString(&leveldir_new->name, leveldir_new->subdir);
3319 if (leveldir_new->identifier == NULL)
3320 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3322 if (leveldir_new->name_sorting == NULL)
3323 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3325 if (node_parent == NULL) /* top level group */
3327 leveldir_new->basepath = getStringCopy(level_directory);
3328 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3330 else /* sub level group */
3332 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3333 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3337 if (leveldir_new->levels < 1)
3338 leveldir_new->levels = 1;
3341 leveldir_new->last_level =
3342 leveldir_new->first_level + leveldir_new->levels - 1;
3344 leveldir_new->in_user_dir =
3345 (!strEqual(leveldir_new->basepath, options.level_directory));
3348 printf("::: '%s' -> %d\n",
3349 leveldir_new->identifier,
3350 leveldir_new->in_user_dir);
3353 /* adjust some settings if user's private level directory was detected */
3354 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3355 leveldir_new->in_user_dir &&
3356 (strEqual(leveldir_new->subdir, getLoginName()) ||
3357 strEqual(leveldir_new->name, getLoginName()) ||
3358 strEqual(leveldir_new->author, getRealName())))
3360 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3361 leveldir_new->readonly = FALSE;
3364 leveldir_new->user_defined =
3365 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3367 leveldir_new->color = LEVELCOLOR(leveldir_new);
3369 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3371 leveldir_new->handicap_level = /* set handicap to default value */
3372 (leveldir_new->user_defined || !leveldir_new->handicap ?
3373 leveldir_new->last_level : leveldir_new->first_level);
3377 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3378 leveldir_new->level_group);
3380 if (leveldir_new->level_group ||
3381 DelayReached(&progress_delay, progress_delay_value))
3382 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3385 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3389 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3391 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3393 /* skip level sets without levels (which are probably artwork base sets) */
3395 freeSetupFileHash(setup_file_hash);
3396 free(directory_path);
3404 pushTreeInfo(node_first, leveldir_new);
3406 freeSetupFileHash(setup_file_hash);
3408 if (leveldir_new->level_group)
3410 /* create node to link back to current level directory */
3411 createParentTreeInfoNode(leveldir_new);
3413 /* recursively step into sub-directory and look for more level series */
3414 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3415 leveldir_new, directory_path);
3418 free(directory_path);
3425 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3426 TreeInfo *node_parent,
3427 char *level_directory)
3430 DirectoryEntry *dir_entry;
3431 boolean valid_entry_found = FALSE;
3434 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3437 if ((dir = openDirectory(level_directory)) == NULL)
3439 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3445 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3448 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3450 char *directory_name = dir_entry->basename;
3451 char *directory_path = getPath2(level_directory, directory_name);
3454 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3457 /* skip entries for current and parent directory */
3458 if (strEqual(directory_name, ".") ||
3459 strEqual(directory_name, ".."))
3461 free(directory_path);
3467 /* find out if directory entry is itself a directory */
3468 if (!dir_entry->is_directory) /* not a directory */
3470 free(directory_path);
3473 Error(ERR_INFO, "* entry '%s' is not a directory ...", directory_name);
3479 /* find out if directory entry is itself a directory */
3480 struct stat file_status;
3481 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3482 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3484 free(directory_path);
3490 free(directory_path);
3492 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3493 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3494 strEqual(directory_name, MUSIC_DIRECTORY))
3497 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3502 closeDirectory(dir);
3504 /* special case: top level directory may directly contain "levelinfo.conf" */
3505 if (node_parent == NULL && !valid_entry_found)
3507 /* check if this directory directly contains a file "levelinfo.conf" */
3508 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3509 level_directory, ".");
3512 if (!valid_entry_found)
3513 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3519 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3520 TreeInfo *node_parent,
3521 char *level_directory)
3524 struct dirent *dir_entry;
3525 boolean valid_entry_found = FALSE;
3528 Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
3531 if ((dir = opendir(level_directory)) == NULL)
3533 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3539 Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
3542 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3544 struct stat file_status;
3545 char *directory_name = dir_entry->d_name;
3546 char *directory_path = getPath2(level_directory, directory_name);
3549 Error(ERR_INFO, "checking entry '%s' ...", directory_name);
3552 /* skip entries for current and parent directory */
3553 if (strEqual(directory_name, ".") ||
3554 strEqual(directory_name, ".."))
3556 free(directory_path);
3560 /* find out if directory entry is itself a directory */
3561 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3562 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3564 free(directory_path);
3568 free(directory_path);
3570 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3571 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3572 strEqual(directory_name, MUSIC_DIRECTORY))
3575 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3582 /* special case: top level directory may directly contain "levelinfo.conf" */
3583 if (node_parent == NULL && !valid_entry_found)
3585 /* check if this directory directly contains a file "levelinfo.conf" */
3586 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3587 level_directory, ".");
3590 if (!valid_entry_found)
3591 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3596 boolean AdjustGraphicsForEMC()
3598 boolean settings_changed = FALSE;
3600 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3601 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3603 return settings_changed;
3606 void LoadLevelInfo()
3608 InitUserLevelDirectory(getLoginName());
3610 DrawInitText("Loading level series", 120, FC_GREEN);
3612 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3613 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3615 /* after loading all level set information, clone the level directory tree
3616 and remove all level sets without levels (these may still contain artwork
3617 to be offered in the setup menu as "custom artwork", and are therefore
3618 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3619 leveldir_first_all = leveldir_first;
3620 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3622 AdjustGraphicsForEMC();
3624 /* before sorting, the first entries will be from the user directory */
3625 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3627 if (leveldir_first == NULL)
3628 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3630 sortTreeInfo(&leveldir_first);
3633 dumpTreeInfo(leveldir_first, 0);
3639 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3640 TreeInfo *node_parent,
3641 char *base_directory,
3642 char *directory_name, int type)
3644 char *directory_path = getPath2(base_directory, directory_name);
3645 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3646 SetupFileHash *setup_file_hash = NULL;
3647 TreeInfo *artwork_new = NULL;
3650 if (fileExists(filename))
3651 setup_file_hash = loadSetupFileHash(filename);
3653 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3656 DirectoryEntry *dir_entry;
3657 boolean valid_file_found = FALSE;
3659 if ((dir = openDirectory(directory_path)) != NULL)
3661 while ((dir_entry = readDirectory(dir)) != NULL)
3663 char *entry_name = dir_entry->basename;
3665 if (FileIsArtworkType(entry_name, type))
3667 valid_file_found = TRUE;
3673 closeDirectory(dir);
3676 if (!valid_file_found)
3678 if (!strEqual(directory_name, "."))
3679 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3681 free(directory_path);
3688 artwork_new = newTreeInfo();
3691 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3693 setTreeInfoToDefaults(artwork_new, type);
3695 artwork_new->subdir = getStringCopy(directory_name);
3697 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3700 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3703 /* set all structure fields according to the token/value pairs */
3705 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3706 setSetupInfo(levelinfo_tokens, i,
3707 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3710 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3711 setString(&artwork_new->name, artwork_new->subdir);
3713 if (artwork_new->identifier == NULL)
3714 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3716 if (artwork_new->name_sorting == NULL)
3717 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3720 if (node_parent == NULL) /* top level group */
3722 artwork_new->basepath = getStringCopy(base_directory);
3723 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3725 else /* sub level group */
3727 artwork_new->basepath = getStringCopy(node_parent->basepath);
3728 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3731 artwork_new->in_user_dir =
3732 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3734 /* (may use ".sort_priority" from "setup_file_hash" above) */
3735 artwork_new->color = ARTWORKCOLOR(artwork_new);
3737 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3739 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3741 if (strEqual(artwork_new->subdir, "."))
3743 if (artwork_new->user_defined)
3745 setString(&artwork_new->identifier, "private");
3746 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3750 setString(&artwork_new->identifier, "classic");
3751 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3754 /* set to new values after changing ".sort_priority" */
3755 artwork_new->color = ARTWORKCOLOR(artwork_new);
3757 setString(&artwork_new->class_desc,
3758 getLevelClassDescription(artwork_new));
3762 setString(&artwork_new->identifier, artwork_new->subdir);
3765 setString(&artwork_new->name, artwork_new->identifier);
3766 setString(&artwork_new->name_sorting, artwork_new->name);
3770 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3773 pushTreeInfo(node_first, artwork_new);
3775 freeSetupFileHash(setup_file_hash);
3777 free(directory_path);
3785 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3786 TreeInfo *node_parent,
3787 char *base_directory,
3788 char *directory_name, int type)
3790 char *directory_path = getPath2(base_directory, directory_name);
3791 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3792 SetupFileHash *setup_file_hash = NULL;
3793 TreeInfo *artwork_new = NULL;
3796 if (fileExists(filename))
3797 setup_file_hash = loadSetupFileHash(filename);
3799 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3802 struct dirent *dir_entry;
3803 boolean valid_file_found = FALSE;
3805 if ((dir = opendir(directory_path)) != NULL)
3807 while ((dir_entry = readdir(dir)) != NULL)
3809 char *entry_name = dir_entry->d_name;
3811 if (FileIsArtworkType(entry_name, type))
3813 valid_file_found = TRUE;
3821 if (!valid_file_found)
3823 if (!strEqual(directory_name, "."))
3824 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3826 free(directory_path);
3833 artwork_new = newTreeInfo();
3836 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3838 setTreeInfoToDefaults(artwork_new, type);
3840 artwork_new->subdir = getStringCopy(directory_name);
3842 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3845 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3848 /* set all structure fields according to the token/value pairs */
3850 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3851 setSetupInfo(levelinfo_tokens, i,
3852 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3855 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3856 setString(&artwork_new->name, artwork_new->subdir);
3858 if (artwork_new->identifier == NULL)
3859 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3861 if (artwork_new->name_sorting == NULL)
3862 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3865 if (node_parent == NULL) /* top level group */
3867 artwork_new->basepath = getStringCopy(base_directory);
3868 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3870 else /* sub level group */
3872 artwork_new->basepath = getStringCopy(node_parent->basepath);
3873 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3876 artwork_new->in_user_dir =
3877 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3879 /* (may use ".sort_priority" from "setup_file_hash" above) */
3880 artwork_new->color = ARTWORKCOLOR(artwork_new);
3882 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3884 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3886 if (strEqual(artwork_new->subdir, "."))
3888 if (artwork_new->user_defined)
3890 setString(&artwork_new->identifier, "private");
3891 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3895 setString(&artwork_new->identifier, "classic");
3896 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3899 /* set to new values after changing ".sort_priority" */
3900 artwork_new->color = ARTWORKCOLOR(artwork_new);
3902 setString(&artwork_new->class_desc,
3903 getLevelClassDescription(artwork_new));
3907 setString(&artwork_new->identifier, artwork_new->subdir);
3910 setString(&artwork_new->name, artwork_new->identifier);
3911 setString(&artwork_new->name_sorting, artwork_new->name);
3915 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3918 pushTreeInfo(node_first, artwork_new);
3920 freeSetupFileHash(setup_file_hash);
3922 free(directory_path);
3932 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3933 TreeInfo *node_parent,
3934 char *base_directory, int type)
3937 DirectoryEntry *dir_entry;
3938 boolean valid_entry_found = FALSE;
3940 if ((dir = openDirectory(base_directory)) == NULL)
3942 /* display error if directory is main "options.graphics_directory" etc. */
3943 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3944 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3949 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3951 char *directory_name = dir_entry->basename;
3952 char *directory_path = getPath2(base_directory, directory_name);
3954 /* skip directory entries for current and parent directory */
3955 if (strEqual(directory_name, ".") ||
3956 strEqual(directory_name, ".."))
3958 free(directory_path);
3964 /* skip directory entries which are not a directory */
3965 if (!dir_entry->is_directory) /* not a directory */
3967 free(directory_path);
3972 /* skip directory entries which are not a directory or are not accessible */
3973 struct stat file_status;
3974 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3975 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3977 free(directory_path);
3983 free(directory_path);
3985 /* check if this directory contains artwork with or without config file */
3986 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3988 directory_name, type);
3991 closeDirectory(dir);
3993 /* check if this directory directly contains artwork itself */
3994 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3995 base_directory, ".",
3997 if (!valid_entry_found)
3998 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4004 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4005 TreeInfo *node_parent,
4006 char *base_directory, int type)
4009 struct dirent *dir_entry;
4010 boolean valid_entry_found = FALSE;
4012 if ((dir = opendir(base_directory)) == NULL)
4014 /* display error if directory is main "options.graphics_directory" etc. */
4015 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4016 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
4021 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
4023 struct stat file_status;
4024 char *directory_name = dir_entry->d_name;
4025 char *directory_path = getPath2(base_directory, directory_name);
4027 /* skip directory entries for current and parent directory */
4028 if (strEqual(directory_name, ".") ||
4029 strEqual(directory_name, ".."))
4031 free(directory_path);
4035 /* skip directory entries which are not a directory or are not accessible */
4036 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
4037 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
4039 free(directory_path);
4043 free(directory_path);
4045 /* check if this directory contains artwork with or without config file */
4046 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4048 directory_name, type);
4053 /* check if this directory directly contains artwork itself */
4054 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4055 base_directory, ".",
4057 if (!valid_entry_found)
4058 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
4064 static TreeInfo *getDummyArtworkInfo(int type)
4066 /* this is only needed when there is completely no artwork available */
4067 TreeInfo *artwork_new = newTreeInfo();
4069 setTreeInfoToDefaults(artwork_new, type);
4071 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4072 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4073 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4075 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4076 setString(&artwork_new->name, UNDEFINED_FILENAME);
4077 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4082 void LoadArtworkInfo()
4084 LoadArtworkInfoCache();
4086 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
4088 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4089 options.graphics_directory,
4090 TREE_TYPE_GRAPHICS_DIR);
4091 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4092 getUserGraphicsDir(),
4093 TREE_TYPE_GRAPHICS_DIR);
4095 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4096 options.sounds_directory,
4097 TREE_TYPE_SOUNDS_DIR);
4098 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4100 TREE_TYPE_SOUNDS_DIR);
4102 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4103 options.music_directory,
4104 TREE_TYPE_MUSIC_DIR);
4105 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4107 TREE_TYPE_MUSIC_DIR);
4109 if (artwork.gfx_first == NULL)
4110 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4111 if (artwork.snd_first == NULL)
4112 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4113 if (artwork.mus_first == NULL)
4114 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4116 /* before sorting, the first entries will be from the user directory */
4117 artwork.gfx_current =
4118 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4119 if (artwork.gfx_current == NULL)
4120 artwork.gfx_current =
4121 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4122 if (artwork.gfx_current == NULL)
4123 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4125 artwork.snd_current =
4126 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4127 if (artwork.snd_current == NULL)
4128 artwork.snd_current =
4129 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4130 if (artwork.snd_current == NULL)
4131 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4133 artwork.mus_current =
4134 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4135 if (artwork.mus_current == NULL)
4136 artwork.mus_current =
4137 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4138 if (artwork.mus_current == NULL)
4139 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4141 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4142 artwork.snd_current_identifier = artwork.snd_current->identifier;
4143 artwork.mus_current_identifier = artwork.mus_current->identifier;
4146 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
4147 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
4148 printf("music set == %s\n\n", artwork.mus_current_identifier);
4151 sortTreeInfo(&artwork.gfx_first);
4152 sortTreeInfo(&artwork.snd_first);
4153 sortTreeInfo(&artwork.mus_first);
4156 dumpTreeInfo(artwork.gfx_first, 0);
4157 dumpTreeInfo(artwork.snd_first, 0);
4158 dumpTreeInfo(artwork.mus_first, 0);
4162 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
4163 LevelDirTree *level_node)
4166 static unsigned int progress_delay = 0;
4167 unsigned int progress_delay_value = 100; /* (in milliseconds) */
4169 int type = (*artwork_node)->type;
4171 /* recursively check all level directories for artwork sub-directories */
4175 /* check all tree entries for artwork, but skip parent link entries */
4176 if (!level_node->parent_link)
4178 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4179 boolean cached = (artwork_new != NULL);
4183 pushTreeInfo(artwork_node, artwork_new);
4187 TreeInfo *topnode_last = *artwork_node;
4188 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4189 ARTWORK_DIRECTORY(type));
4191 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4193 if (topnode_last != *artwork_node) /* check for newly added node */
4195 artwork_new = *artwork_node;
4197 setString(&artwork_new->identifier, level_node->subdir);
4198 setString(&artwork_new->name, level_node->name);
4199 setString(&artwork_new->name_sorting, level_node->name_sorting);
4201 artwork_new->sort_priority = level_node->sort_priority;
4202 artwork_new->color = LEVELCOLOR(artwork_new);
4208 /* insert artwork info (from old cache or filesystem) into new cache */
4209 if (artwork_new != NULL)
4210 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4214 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
4215 level_node->level_group);
4217 if (level_node->level_group ||
4218 DelayReached(&progress_delay, progress_delay_value))
4219 DrawInitText(level_node->name, 150, FC_YELLOW);
4222 if (level_node->node_group != NULL)
4223 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
4225 level_node = level_node->next;
4229 void LoadLevelArtworkInfo()
4231 print_timestamp_init("LoadLevelArtworkInfo");
4233 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4235 print_timestamp_time("DrawTimeText");
4237 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
4238 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4239 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
4240 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4241 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
4242 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4244 SaveArtworkInfoCache();
4246 print_timestamp_time("SaveArtworkInfoCache");
4248 /* needed for reloading level artwork not known at ealier stage */
4250 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
4252 artwork.gfx_current =
4253 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
4254 if (artwork.gfx_current == NULL)
4255 artwork.gfx_current =
4256 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
4257 if (artwork.gfx_current == NULL)
4258 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
4261 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
4263 artwork.snd_current =
4264 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
4265 if (artwork.snd_current == NULL)
4266 artwork.snd_current =
4267 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
4268 if (artwork.snd_current == NULL)
4269 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
4272 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
4274 artwork.mus_current =
4275 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
4276 if (artwork.mus_current == NULL)
4277 artwork.mus_current =
4278 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
4279 if (artwork.mus_current == NULL)
4280 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
4283 print_timestamp_time("getTreeInfoFromIdentifier");
4285 sortTreeInfo(&artwork.gfx_first);
4286 sortTreeInfo(&artwork.snd_first);
4287 sortTreeInfo(&artwork.mus_first);
4289 print_timestamp_time("sortTreeInfo");
4292 dumpTreeInfo(artwork.gfx_first, 0);
4293 dumpTreeInfo(artwork.snd_first, 0);
4294 dumpTreeInfo(artwork.mus_first, 0);
4297 print_timestamp_done("LoadLevelArtworkInfo");
4300 static void SaveUserLevelInfo()
4302 LevelDirTree *level_info;
4307 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
4309 if (!(file = fopen(filename, MODE_WRITE)))
4311 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4316 level_info = newTreeInfo();
4318 /* always start with reliable default values */
4319 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4321 setString(&level_info->name, getLoginName());
4322 setString(&level_info->author, getRealName());
4323 level_info->levels = 100;
4324 level_info->first_level = 1;
4326 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4328 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4329 getCookie("LEVELINFO")));
4332 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4334 if (i == LEVELINFO_TOKEN_NAME ||
4335 i == LEVELINFO_TOKEN_AUTHOR ||
4336 i == LEVELINFO_TOKEN_LEVELS ||
4337 i == LEVELINFO_TOKEN_FIRST_LEVEL)
4338 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4340 /* just to make things nicer :) */
4341 if (i == LEVELINFO_TOKEN_AUTHOR)
4342 fprintf(file, "\n");
4345 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4349 SetFilePermissions(filename, PERMS_PRIVATE);
4351 freeTreeInfo(level_info);
4355 char *getSetupValue(int type, void *value)
4357 static char value_string[MAX_LINE_LEN];
4365 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4369 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4373 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4374 *(int *)value == FALSE ? "off" : "on"));
4378 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4381 case TYPE_YES_NO_AUTO:
4382 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4383 *(int *)value == FALSE ? "no" : "yes"));
4387 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4391 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4395 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4399 sprintf(value_string, "%d", *(int *)value);
4403 if (*(char **)value == NULL)
4406 strcpy(value_string, *(char **)value);
4410 value_string[0] = '\0';
4414 if (type & TYPE_GHOSTED)
4415 strcpy(value_string, "n/a");
4417 return value_string;
4420 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4424 static char token_string[MAX_LINE_LEN];
4425 int token_type = token_info[token_nr].type;
4426 void *setup_value = token_info[token_nr].value;
4427 char *token_text = token_info[token_nr].text;
4428 char *value_string = getSetupValue(token_type, setup_value);
4430 /* build complete token string */
4431 sprintf(token_string, "%s%s", prefix, token_text);
4433 /* build setup entry line */
4434 line = getFormattedSetupEntry(token_string, value_string);
4436 if (token_type == TYPE_KEY_X11)
4438 Key key = *(Key *)setup_value;
4439 char *keyname = getKeyNameFromKey(key);
4441 /* add comment, if useful */
4442 if (!strEqual(keyname, "(undefined)") &&
4443 !strEqual(keyname, "(unknown)"))
4445 /* add at least one whitespace */
4447 for (i = strlen(line); i < token_comment_position; i++)
4451 strcat(line, keyname);
4458 void LoadLevelSetup_LastSeries()
4460 /* ----------------------------------------------------------------------- */
4461 /* ~/.<program>/levelsetup.conf */
4462 /* ----------------------------------------------------------------------- */
4464 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4465 SetupFileHash *level_setup_hash = NULL;
4467 /* always start with reliable default values */
4468 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4470 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
4471 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4473 if (leveldir_current == NULL)
4474 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4477 if ((level_setup_hash = loadSetupFileHash(filename)))
4479 char *last_level_series =
4480 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4482 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4484 if (leveldir_current == NULL)
4485 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4487 checkSetupFileHashIdentifier(level_setup_hash, filename,
4488 getCookie("LEVELSETUP"));
4490 freeSetupFileHash(level_setup_hash);
4493 Error(ERR_WARN, "using default setup values");
4498 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4500 /* ----------------------------------------------------------------------- */
4501 /* ~/.<program>/levelsetup.conf */
4502 /* ----------------------------------------------------------------------- */
4504 // check if the current level directory structure is available at this point
4505 if (leveldir_current == NULL)
4508 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4509 char *level_subdir = leveldir_current->subdir;
4512 InitUserDataDirectory();
4514 if (!(file = fopen(filename, MODE_WRITE)))
4516 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4523 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4524 getCookie("LEVELSETUP")));
4526 if (deactivate_last_level_series)
4527 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4529 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4534 SetFilePermissions(filename, PERMS_PRIVATE);
4539 void SaveLevelSetup_LastSeries()
4541 SaveLevelSetup_LastSeries_Ext(FALSE);
4544 void SaveLevelSetup_LastSeries_Deactivate()
4546 SaveLevelSetup_LastSeries_Ext(TRUE);
4551 static void checkSeriesInfo()
4553 static char *level_directory = NULL;
4556 DirectoryEntry *dir_entry;
4559 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4561 level_directory = getPath2((leveldir_current->in_user_dir ?
4562 getUserLevelDir(NULL) :
4563 options.level_directory),
4564 leveldir_current->fullpath);
4566 if ((dir = openDirectory(level_directory)) == NULL)
4568 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4574 while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */
4576 if (strlen(dir_entry->basename) > 4 &&
4577 dir_entry->basename[3] == '.' &&
4578 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4580 char levelnum_str[4];
4583 strncpy(levelnum_str, dir_entry->basename, 3);
4584 levelnum_str[3] = '\0';
4586 levelnum_value = atoi(levelnum_str);
4588 if (levelnum_value < leveldir_current->first_level)
4590 Error(ERR_WARN, "additional level %d found", levelnum_value);
4591 leveldir_current->first_level = levelnum_value;
4593 else if (levelnum_value > leveldir_current->last_level)
4595 Error(ERR_WARN, "additional level %d found", levelnum_value);
4596 leveldir_current->last_level = levelnum_value;
4602 closeDirectory(dir);
4607 static void checkSeriesInfo()
4609 static char *level_directory = NULL;
4612 struct dirent *dir_entry;
4615 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4617 level_directory = getPath2((leveldir_current->in_user_dir ?
4618 getUserLevelDir(NULL) :
4619 options.level_directory),
4620 leveldir_current->fullpath);
4622 if ((dir = opendir(level_directory)) == NULL)
4624 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4630 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
4632 if (strlen(dir_entry->d_name) > 4 &&
4633 dir_entry->d_name[3] == '.' &&
4634 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4636 char levelnum_str[4];
4639 strncpy(levelnum_str, dir_entry->d_name, 3);
4640 levelnum_str[3] = '\0';
4642 levelnum_value = atoi(levelnum_str);
4644 if (levelnum_value < leveldir_current->first_level)
4646 Error(ERR_WARN, "additional level %d found", levelnum_value);
4647 leveldir_current->first_level = levelnum_value;
4649 else if (levelnum_value > leveldir_current->last_level)
4651 Error(ERR_WARN, "additional level %d found", levelnum_value);
4652 leveldir_current->last_level = levelnum_value;
4663 void LoadLevelSetup_SeriesInfo()
4666 SetupFileHash *level_setup_hash = NULL;
4667 char *level_subdir = leveldir_current->subdir;
4670 /* always start with reliable default values */
4671 level_nr = leveldir_current->first_level;
4673 for (i = 0; i < MAX_LEVELS; i++)
4675 LevelStats_setPlayed(i, 0);
4676 LevelStats_setSolved(i, 0);
4679 checkSeriesInfo(leveldir_current);
4681 /* ----------------------------------------------------------------------- */
4682 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4683 /* ----------------------------------------------------------------------- */
4685 level_subdir = leveldir_current->subdir;
4687 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4689 if ((level_setup_hash = loadSetupFileHash(filename)))
4693 /* get last played level in this level set */
4695 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4699 level_nr = atoi(token_value);
4701 if (level_nr < leveldir_current->first_level)
4702 level_nr = leveldir_current->first_level;
4703 if (level_nr > leveldir_current->last_level)
4704 level_nr = leveldir_current->last_level;
4707 /* get handicap level in this level set */
4709 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4713 int level_nr = atoi(token_value);
4715 if (level_nr < leveldir_current->first_level)
4716 level_nr = leveldir_current->first_level;
4717 if (level_nr > leveldir_current->last_level + 1)
4718 level_nr = leveldir_current->last_level;
4720 if (leveldir_current->user_defined || !leveldir_current->handicap)
4721 level_nr = leveldir_current->last_level;
4723 leveldir_current->handicap_level = level_nr;
4726 /* get number of played and solved levels in this level set */
4728 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4730 char *token = HASH_ITERATION_TOKEN(itr);
4731 char *value = HASH_ITERATION_VALUE(itr);
4733 if (strlen(token) == 3 &&
4734 token[0] >= '0' && token[0] <= '9' &&
4735 token[1] >= '0' && token[1] <= '9' &&
4736 token[2] >= '0' && token[2] <= '9')
4738 int level_nr = atoi(token);
4741 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4743 value = strchr(value, ' ');
4746 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4749 END_HASH_ITERATION(hash, itr)
4751 checkSetupFileHashIdentifier(level_setup_hash, filename,
4752 getCookie("LEVELSETUP"));
4754 freeSetupFileHash(level_setup_hash);
4757 Error(ERR_WARN, "using default setup values");
4762 void SaveLevelSetup_SeriesInfo()
4765 char *level_subdir = leveldir_current->subdir;
4766 char *level_nr_str = int2str(level_nr, 0);
4767 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4771 /* ----------------------------------------------------------------------- */
4772 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4773 /* ----------------------------------------------------------------------- */
4775 InitLevelSetupDirectory(level_subdir);
4777 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4779 if (!(file = fopen(filename, MODE_WRITE)))
4781 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4786 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4787 getCookie("LEVELSETUP")));
4788 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4790 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4791 handicap_level_str));
4793 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4796 if (LevelStats_getPlayed(i) > 0 ||
4797 LevelStats_getSolved(i) > 0)
4802 sprintf(token, "%03d", i);
4803 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4805 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4811 SetFilePermissions(filename, PERMS_PRIVATE);
4816 int LevelStats_getPlayed(int nr)
4818 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4821 int LevelStats_getSolved(int nr)
4823 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4826 void LevelStats_setPlayed(int nr, int value)
4828 if (nr >= 0 && nr < MAX_LEVELS)
4829 level_stats[nr].played = value;
4832 void LevelStats_setSolved(int nr, int value)
4834 if (nr >= 0 && nr < MAX_LEVELS)
4835 level_stats[nr].solved = value;
4838 void LevelStats_incPlayed(int nr)
4840 if (nr >= 0 && nr < MAX_LEVELS)
4841 level_stats[nr].played++;
4844 void LevelStats_incSolved(int nr)
4846 if (nr >= 0 && nr < MAX_LEVELS)
4847 level_stats[nr].solved++;