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>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getDefaultArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getDefaultArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
341 normally result in using the artwork configured in the setup menu. But
342 if an artwork subdirectory exists (which might contain custom artwork
343 or an artwork configuration file), this level artwork must be treated
344 as relative to the default "classic" artwork, not to the artwork that
345 is currently configured in the setup menu. */
347 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
349 checked_free(*artwork_set_ptr);
353 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
354 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
358 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
359 *artwork_set_ptr = NULL;
365 return *artwork_set_ptr;
368 inline static char *getLevelArtworkSet(int type)
370 if (leveldir_current == NULL)
373 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
376 inline static char *getLevelArtworkDir(int type)
378 if (leveldir_current == NULL)
379 return UNDEFINED_FILENAME;
381 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
384 char *getTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
397 char *getSolutionTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getSolutionTapeDir(), basename);
410 char *getScoreFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
418 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
423 char *getSetupFilename()
425 static char *filename = NULL;
427 checked_free(filename);
429 filename = getPath2(getSetupDir(), SETUP_FILENAME);
434 char *getEditorSetupFilename()
436 static char *filename = NULL;
438 checked_free(filename);
439 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
441 if (fileExists(filename))
444 checked_free(filename);
445 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
450 char *getHelpAnimFilename()
452 static char *filename = NULL;
454 checked_free(filename);
456 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
461 char *getHelpTextFilename()
463 static char *filename = NULL;
465 checked_free(filename);
467 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
472 char *getLevelSetInfoFilename()
474 static char *filename = NULL;
489 for (i = 0; basenames[i] != NULL; i++)
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), basenames[i]);
494 if (fileExists(filename))
501 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
503 static char basename[32];
505 sprintf(basename, "%s_%d.txt",
506 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
511 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
513 static char *filename = NULL;
515 boolean skip_setup_artwork = FALSE;
517 checked_free(filename);
519 basename = getLevelSetTitleMessageBasename(nr, initial);
521 if (!setup.override_level_graphics)
523 /* 1st try: look for special artwork in current level series directory */
524 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
525 if (fileExists(filename))
530 /* 2nd try: look for message file in current level set directory */
531 filename = getPath2(getCurrentLevelDir(), basename);
532 if (fileExists(filename))
537 /* check if there is special artwork configured in level series config */
538 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
540 /* 3rd try: look for special artwork configured in level series config */
541 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
542 if (fileExists(filename))
547 /* take missing artwork configured in level set config from default */
548 skip_setup_artwork = TRUE;
552 if (!skip_setup_artwork)
554 /* 4th try: look for special artwork in configured artwork directory */
555 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
556 if (fileExists(filename))
562 /* 5th try: look for default artwork in new default artwork directory */
563 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
564 if (fileExists(filename))
569 /* 6th try: look for default artwork in old default artwork directory */
570 filename = getPath2(options.graphics_directory, basename);
571 if (fileExists(filename))
574 return NULL; /* cannot find specified artwork file anywhere */
577 static char *getCorrectedArtworkBasename(char *basename)
579 char *basename_corrected = basename;
581 #if defined(PLATFORM_MSDOS)
582 if (program.filename_prefix != NULL)
584 int prefix_len = strlen(program.filename_prefix);
586 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
587 basename_corrected = &basename[prefix_len];
589 /* if corrected filename is still longer than standard MS-DOS filename
590 size (8 characters + 1 dot + 3 characters file extension), shorten
591 filename by writing file extension after 8th basename character */
592 if (strlen(basename_corrected) > 8 + 1 + 3)
594 static char *msdos_filename = NULL;
596 checked_free(msdos_filename);
598 msdos_filename = getStringCopy(basename_corrected);
599 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
601 basename_corrected = msdos_filename;
606 return basename_corrected;
609 char *getCustomImageFilename(char *basename)
611 static char *filename = NULL;
612 boolean skip_setup_artwork = FALSE;
614 checked_free(filename);
616 basename = getCorrectedArtworkBasename(basename);
618 if (!setup.override_level_graphics)
620 /* 1st try: look for special artwork in current level series directory */
621 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
622 if (fileExists(filename))
627 /* check if there is special artwork configured in level series config */
628 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
630 /* 2nd try: look for special artwork configured in level series config */
631 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
632 if (fileExists(filename))
637 /* take missing artwork configured in level set config from default */
638 skip_setup_artwork = TRUE;
642 if (!skip_setup_artwork)
644 /* 3rd try: look for special artwork in configured artwork directory */
645 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
646 if (fileExists(filename))
652 /* 4th try: look for default artwork in new default artwork directory */
653 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
654 if (fileExists(filename))
659 /* 5th try: look for default artwork in old default artwork directory */
660 filename = getPath2(options.graphics_directory, basename);
661 if (fileExists(filename))
664 #if CREATE_SPECIAL_EDITION
667 /* !!! INSERT WARNING HERE TO REPORT MISSING ARTWORK FILES !!! */
669 printf("::: MISSING ARTWORK FILE '%s'\n", basename);
672 /* 6th try: look for fallback artwork in old default artwork directory */
673 /* (needed to prevent errors when trying to access unused artwork files) */
674 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
675 if (fileExists(filename))
679 return NULL; /* cannot find specified artwork file anywhere */
682 char *getCustomSoundFilename(char *basename)
684 static char *filename = NULL;
685 boolean skip_setup_artwork = FALSE;
687 checked_free(filename);
689 basename = getCorrectedArtworkBasename(basename);
691 if (!setup.override_level_sounds)
693 /* 1st try: look for special artwork in current level series directory */
694 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
695 if (fileExists(filename))
700 /* check if there is special artwork configured in level series config */
701 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
703 /* 2nd try: look for special artwork configured in level series config */
704 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
705 if (fileExists(filename))
710 /* take missing artwork configured in level set config from default */
711 skip_setup_artwork = TRUE;
715 if (!skip_setup_artwork)
717 /* 3rd try: look for special artwork in configured artwork directory */
718 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
719 if (fileExists(filename))
725 /* 4th try: look for default artwork in new default artwork directory */
726 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
727 if (fileExists(filename))
732 /* 5th try: look for default artwork in old default artwork directory */
733 filename = getPath2(options.sounds_directory, basename);
734 if (fileExists(filename))
737 #if CREATE_SPECIAL_EDITION
740 /* 6th try: look for fallback artwork in old default artwork directory */
741 /* (needed to prevent errors when trying to access unused artwork files) */
742 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
743 if (fileExists(filename))
747 return NULL; /* cannot find specified artwork file anywhere */
750 char *getCustomMusicFilename(char *basename)
752 static char *filename = NULL;
753 boolean skip_setup_artwork = FALSE;
755 checked_free(filename);
757 basename = getCorrectedArtworkBasename(basename);
759 if (!setup.override_level_music)
761 /* 1st try: look for special artwork in current level series directory */
762 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
763 if (fileExists(filename))
768 /* check if there is special artwork configured in level series config */
769 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
771 /* 2nd try: look for special artwork configured in level series config */
772 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
773 if (fileExists(filename))
778 /* take missing artwork configured in level set config from default */
779 skip_setup_artwork = TRUE;
783 if (!skip_setup_artwork)
785 /* 3rd try: look for special artwork in configured artwork directory */
786 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
787 if (fileExists(filename))
793 /* 4th try: look for default artwork in new default artwork directory */
794 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
795 if (fileExists(filename))
800 /* 5th try: look for default artwork in old default artwork directory */
801 filename = getPath2(options.music_directory, basename);
802 if (fileExists(filename))
805 #if CREATE_SPECIAL_EDITION
808 /* 6th try: look for fallback artwork in old default artwork directory */
809 /* (needed to prevent errors when trying to access unused artwork files) */
810 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
811 if (fileExists(filename))
815 return NULL; /* cannot find specified artwork file anywhere */
818 char *getCustomArtworkFilename(char *basename, int type)
820 if (type == ARTWORK_TYPE_GRAPHICS)
821 return getCustomImageFilename(basename);
822 else if (type == ARTWORK_TYPE_SOUNDS)
823 return getCustomSoundFilename(basename);
824 else if (type == ARTWORK_TYPE_MUSIC)
825 return getCustomMusicFilename(basename);
827 return UNDEFINED_FILENAME;
830 char *getCustomArtworkConfigFilename(int type)
832 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
835 char *getCustomArtworkLevelConfigFilename(int type)
837 static char *filename = NULL;
839 checked_free(filename);
841 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
846 char *getCustomMusicDirectory(void)
848 static char *directory = NULL;
849 boolean skip_setup_artwork = FALSE;
851 checked_free(directory);
853 if (!setup.override_level_music)
855 /* 1st try: look for special artwork in current level series directory */
856 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
857 if (fileExists(directory))
862 /* check if there is special artwork configured in level series config */
863 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
865 /* 2nd try: look for special artwork configured in level series config */
866 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
867 if (fileExists(directory))
872 /* take missing artwork configured in level set config from default */
873 skip_setup_artwork = TRUE;
877 if (!skip_setup_artwork)
879 /* 3rd try: look for special artwork in configured artwork directory */
880 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
881 if (fileExists(directory))
887 /* 4th try: look for default artwork in new default artwork directory */
888 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
889 if (fileExists(directory))
894 /* 5th try: look for default artwork in old default artwork directory */
895 directory = getStringCopy(options.music_directory);
896 if (fileExists(directory))
899 return NULL; /* cannot find specified artwork file anywhere */
902 void InitTapeDirectory(char *level_subdir)
904 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
905 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
906 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
909 void InitScoreDirectory(char *level_subdir)
911 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
912 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
913 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
916 static void SaveUserLevelInfo();
918 void InitUserLevelDirectory(char *level_subdir)
920 if (!fileExists(getUserLevelDir(level_subdir)))
922 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
923 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
924 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
930 void InitLevelSetupDirectory(char *level_subdir)
932 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
933 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
934 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
937 void InitCacheDirectory()
939 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
940 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
944 /* ------------------------------------------------------------------------- */
945 /* some functions to handle lists of level and artwork directories */
946 /* ------------------------------------------------------------------------- */
948 TreeInfo *newTreeInfo()
950 return checked_calloc(sizeof(TreeInfo));
953 TreeInfo *newTreeInfo_setDefaults(int type)
955 TreeInfo *ti = newTreeInfo();
957 setTreeInfoToDefaults(ti, type);
962 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
964 node_new->next = *node_first;
965 *node_first = node_new;
968 int numTreeInfo(TreeInfo *node)
981 boolean validLevelSeries(TreeInfo *node)
983 return (node != NULL && !node->node_group && !node->parent_link);
986 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
991 if (node->node_group) /* enter level group (step down into tree) */
992 return getFirstValidTreeInfoEntry(node->node_group);
993 else if (node->parent_link) /* skip start entry of level group */
995 if (node->next) /* get first real level series entry */
996 return getFirstValidTreeInfoEntry(node->next);
997 else /* leave empty level group and go on */
998 return getFirstValidTreeInfoEntry(node->node_parent->next);
1000 else /* this seems to be a regular level series */
1004 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1009 if (node->node_parent == NULL) /* top level group */
1010 return *node->node_top;
1011 else /* sub level group */
1012 return node->node_parent->node_group;
1015 int numTreeInfoInGroup(TreeInfo *node)
1017 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1020 int posTreeInfo(TreeInfo *node)
1022 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1027 if (node_cmp == node)
1031 node_cmp = node_cmp->next;
1037 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1039 TreeInfo *node_default = node;
1051 return node_default;
1054 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1056 if (identifier == NULL)
1061 if (node->node_group)
1063 TreeInfo *node_group;
1065 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1070 else if (!node->parent_link)
1072 if (strEqual(identifier, node->identifier))
1082 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1083 TreeInfo *node, boolean skip_sets_without_levels)
1090 if (!node->parent_link && !node->level_group &&
1091 skip_sets_without_levels && node->levels == 0)
1092 return cloneTreeNode(node_top, node_parent, node->next,
1093 skip_sets_without_levels);
1096 node_new = getTreeInfoCopy(node); /* copy complete node */
1098 node_new = newTreeInfo();
1100 *node_new = *node; /* copy complete node */
1103 node_new->node_top = node_top; /* correct top node link */
1104 node_new->node_parent = node_parent; /* correct parent node link */
1106 if (node->level_group)
1107 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1108 skip_sets_without_levels);
1110 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1111 skip_sets_without_levels);
1116 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1118 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1120 *ti_new = ti_cloned;
1123 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1125 boolean settings_changed = FALSE;
1129 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1130 !strEqual(node->graphics_set, node->graphics_set_ecs))
1132 setString(&node->graphics_set, node->graphics_set_ecs);
1133 settings_changed = TRUE;
1135 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1136 !strEqual(node->graphics_set, node->graphics_set_aga))
1138 setString(&node->graphics_set, node->graphics_set_aga);
1139 settings_changed = TRUE;
1142 if (node->node_group != NULL)
1143 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1148 return settings_changed;
1151 void dumpTreeInfo(TreeInfo *node, int depth)
1155 printf("Dumping TreeInfo:\n");
1159 for (i = 0; i < (depth + 1) * 3; i++)
1162 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1163 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1165 if (node->node_group != NULL)
1166 dumpTreeInfo(node->node_group, depth + 1);
1172 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1173 int (*compare_function)(const void *,
1176 int num_nodes = numTreeInfo(*node_first);
1177 TreeInfo **sort_array;
1178 TreeInfo *node = *node_first;
1184 /* allocate array for sorting structure pointers */
1185 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1187 /* writing structure pointers to sorting array */
1188 while (i < num_nodes && node) /* double boundary check... */
1190 sort_array[i] = node;
1196 /* sorting the structure pointers in the sorting array */
1197 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1200 /* update the linkage of list elements with the sorted node array */
1201 for (i = 0; i < num_nodes - 1; i++)
1202 sort_array[i]->next = sort_array[i + 1];
1203 sort_array[num_nodes - 1]->next = NULL;
1205 /* update the linkage of the main list anchor pointer */
1206 *node_first = sort_array[0];
1210 /* now recursively sort the level group structures */
1214 if (node->node_group != NULL)
1215 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1221 void sortTreeInfo(TreeInfo **node_first)
1223 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1227 /* ========================================================================= */
1228 /* some stuff from "files.c" */
1229 /* ========================================================================= */
1231 #if defined(PLATFORM_WIN32)
1233 #define S_IRGRP S_IRUSR
1236 #define S_IROTH S_IRUSR
1239 #define S_IWGRP S_IWUSR
1242 #define S_IWOTH S_IWUSR
1245 #define S_IXGRP S_IXUSR
1248 #define S_IXOTH S_IXUSR
1251 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1256 #endif /* PLATFORM_WIN32 */
1258 /* file permissions for newly written files */
1259 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1260 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1261 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1263 #define MODE_W_PRIVATE (S_IWUSR)
1264 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1265 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1267 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1268 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1270 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1271 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1275 static char *dir = NULL;
1277 #if defined(PLATFORM_WIN32)
1280 dir = checked_malloc(MAX_PATH + 1);
1282 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1285 #elif defined(PLATFORM_UNIX)
1288 if ((dir = getenv("HOME")) == NULL)
1292 if ((pwd = getpwuid(getuid())) != NULL)
1293 dir = getStringCopy(pwd->pw_dir);
1305 char *getCommonDataDir(void)
1307 static char *common_data_dir = NULL;
1309 #if defined(PLATFORM_WIN32)
1310 if (common_data_dir == NULL)
1312 char *dir = checked_malloc(MAX_PATH + 1);
1314 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1315 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1316 common_data_dir = getPath2(dir, program.userdata_subdir);
1318 common_data_dir = options.rw_base_directory;
1321 if (common_data_dir == NULL)
1322 common_data_dir = options.rw_base_directory;
1325 return common_data_dir;
1328 char *getPersonalDataDir(void)
1330 static char *personal_data_dir = NULL;
1332 #if defined(PLATFORM_MACOSX)
1333 if (personal_data_dir == NULL)
1334 personal_data_dir = getPath2(getHomeDir(), "Documents");
1336 if (personal_data_dir == NULL)
1337 personal_data_dir = getHomeDir();
1340 return personal_data_dir;
1343 char *getUserGameDataDir(void)
1345 static char *user_game_data_dir = NULL;
1347 if (user_game_data_dir == NULL)
1348 user_game_data_dir = getPath2(getPersonalDataDir(),
1349 program.userdata_subdir);
1351 return user_game_data_dir;
1354 void updateUserGameDataDir()
1356 #if defined(PLATFORM_MACOSX)
1357 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1358 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1360 /* convert old Unix style game data directory to Mac OS X style, if needed */
1361 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1363 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1365 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1366 userdata_dir_old, userdata_dir_new);
1368 /* continue using Unix style data directory -- this should not happen */
1369 program.userdata_path = getPath2(getPersonalDataDir(),
1370 program.userdata_subdir_unix);
1374 free(userdata_dir_old);
1380 return getUserGameDataDir();
1383 static mode_t posix_umask(mode_t mask)
1385 #if defined(PLATFORM_UNIX)
1392 static int posix_mkdir(const char *pathname, mode_t mode)
1394 #if defined(PLATFORM_WIN32)
1395 return mkdir(pathname);
1397 return mkdir(pathname, mode);
1401 void createDirectory(char *dir, char *text, int permission_class)
1403 /* leave "other" permissions in umask untouched, but ensure group parts
1404 of USERDATA_DIR_MODE are not masked */
1405 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1406 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1407 mode_t normal_umask = posix_umask(0);
1408 mode_t group_umask = ~(dir_mode & S_IRWXG);
1409 posix_umask(normal_umask & group_umask);
1411 if (!fileExists(dir))
1412 if (posix_mkdir(dir, dir_mode) != 0)
1413 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1415 posix_umask(normal_umask); /* reset normal umask */
1418 void InitUserDataDirectory()
1420 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1423 void SetFilePermissions(char *filename, int permission_class)
1425 chmod(filename, (permission_class == PERMS_PRIVATE ?
1426 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1429 char *getCookie(char *file_type)
1431 static char cookie[MAX_COOKIE_LEN + 1];
1433 if (strlen(program.cookie_prefix) + 1 +
1434 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1435 return "[COOKIE ERROR]"; /* should never happen */
1437 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1438 program.cookie_prefix, file_type,
1439 program.version_major, program.version_minor);
1444 int getFileVersionFromCookieString(const char *cookie)
1446 const char *ptr_cookie1, *ptr_cookie2;
1447 const char *pattern1 = "_FILE_VERSION_";
1448 const char *pattern2 = "?.?";
1449 const int len_cookie = strlen(cookie);
1450 const int len_pattern1 = strlen(pattern1);
1451 const int len_pattern2 = strlen(pattern2);
1452 const int len_pattern = len_pattern1 + len_pattern2;
1453 int version_major, version_minor;
1455 if (len_cookie <= len_pattern)
1458 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1459 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1461 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1464 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1465 ptr_cookie2[1] != '.' ||
1466 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1469 version_major = ptr_cookie2[0] - '0';
1470 version_minor = ptr_cookie2[2] - '0';
1472 return VERSION_IDENT(version_major, version_minor, 0, 0);
1475 boolean checkCookieString(const char *cookie, const char *template)
1477 const char *pattern = "_FILE_VERSION_?.?";
1478 const int len_cookie = strlen(cookie);
1479 const int len_template = strlen(template);
1480 const int len_pattern = strlen(pattern);
1482 if (len_cookie != len_template)
1485 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1491 /* ------------------------------------------------------------------------- */
1492 /* setup file list and hash handling functions */
1493 /* ------------------------------------------------------------------------- */
1495 char *getFormattedSetupEntry(char *token, char *value)
1498 static char entry[MAX_LINE_LEN];
1500 /* if value is an empty string, just return token without value */
1504 /* start with the token and some spaces to format output line */
1505 sprintf(entry, "%s:", token);
1506 for (i = strlen(entry); i < token_value_position; i++)
1509 /* continue with the token's value */
1510 strcat(entry, value);
1515 SetupFileList *newSetupFileList(char *token, char *value)
1517 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1519 new->token = getStringCopy(token);
1520 new->value = getStringCopy(value);
1527 void freeSetupFileList(SetupFileList *list)
1532 checked_free(list->token);
1533 checked_free(list->value);
1536 freeSetupFileList(list->next);
1541 char *getListEntry(SetupFileList *list, char *token)
1546 if (strEqual(list->token, token))
1549 return getListEntry(list->next, token);
1552 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1557 if (strEqual(list->token, token))
1559 checked_free(list->value);
1561 list->value = getStringCopy(value);
1565 else if (list->next == NULL)
1566 return (list->next = newSetupFileList(token, value));
1568 return setListEntry(list->next, token, value);
1571 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1576 if (list->next == NULL)
1577 return (list->next = newSetupFileList(token, value));
1579 return addListEntry(list->next, token, value);
1583 static void printSetupFileList(SetupFileList *list)
1588 printf("token: '%s'\n", list->token);
1589 printf("value: '%s'\n", list->value);
1591 printSetupFileList(list->next);
1596 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1597 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1598 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1599 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1601 #define insert_hash_entry hashtable_insert
1602 #define search_hash_entry hashtable_search
1603 #define change_hash_entry hashtable_change
1604 #define remove_hash_entry hashtable_remove
1607 static unsigned int get_hash_from_key(void *key)
1612 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1613 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1614 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1615 it works better than many other constants, prime or not) has never been
1616 adequately explained.
1618 If you just want to have a good hash function, and cannot wait, djb2
1619 is one of the best string hash functions i know. It has excellent
1620 distribution and speed on many different sets of keys and table sizes.
1621 You are not likely to do better with one of the "well known" functions
1622 such as PJW, K&R, etc.
1624 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1627 char *str = (char *)key;
1628 unsigned int hash = 5381;
1631 while ((c = *str++))
1632 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1637 static int keys_are_equal(void *key1, void *key2)
1639 return (strEqual((char *)key1, (char *)key2));
1642 SetupFileHash *newSetupFileHash()
1644 SetupFileHash *new_hash =
1645 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1647 if (new_hash == NULL)
1648 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1653 void freeSetupFileHash(SetupFileHash *hash)
1658 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1661 char *getHashEntry(SetupFileHash *hash, char *token)
1666 return search_hash_entry(hash, token);
1669 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1676 value_copy = getStringCopy(value);
1678 /* change value; if it does not exist, insert it as new */
1679 if (!change_hash_entry(hash, token, value_copy))
1680 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1681 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1684 char *removeHashEntry(SetupFileHash *hash, char *token)
1689 return remove_hash_entry(hash, token);
1693 static void printSetupFileHash(SetupFileHash *hash)
1695 BEGIN_HASH_ITERATION(hash, itr)
1697 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1698 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1700 END_HASH_ITERATION(hash, itr)
1704 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1705 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1706 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1708 static boolean token_value_separator_found = FALSE;
1709 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1710 static boolean token_value_separator_warning = FALSE;
1712 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1713 static boolean token_already_exists_warning = FALSE;
1716 static boolean getTokenValueFromSetupLineExt(char *line,
1717 char **token_ptr, char **value_ptr,
1718 char *filename, char *line_raw,
1720 boolean separator_required)
1722 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1723 char *token, *value, *line_ptr;
1725 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1726 if (line_raw == NULL)
1728 strncpy(line_copy, line, MAX_LINE_LEN);
1729 line_copy[MAX_LINE_LEN] = '\0';
1732 strcpy(line_raw_copy, line_copy);
1733 line_raw = line_raw_copy;
1736 /* cut trailing comment from input line */
1737 for (line_ptr = line; *line_ptr; line_ptr++)
1739 if (*line_ptr == '#')
1746 /* cut trailing whitespaces from input line */
1747 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1748 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1751 /* ignore empty lines */
1755 /* cut leading whitespaces from token */
1756 for (token = line; *token; token++)
1757 if (*token != ' ' && *token != '\t')
1760 /* start with empty value as reliable default */
1763 token_value_separator_found = FALSE;
1765 /* find end of token to determine start of value */
1766 for (line_ptr = token; *line_ptr; line_ptr++)
1769 /* first look for an explicit token/value separator, like ':' or '=' */
1770 if (*line_ptr == ':' || *line_ptr == '=')
1772 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1775 *line_ptr = '\0'; /* terminate token string */
1776 value = line_ptr + 1; /* set beginning of value */
1778 token_value_separator_found = TRUE;
1784 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1785 /* fallback: if no token/value separator found, also allow whitespaces */
1786 if (!token_value_separator_found && !separator_required)
1788 for (line_ptr = token; *line_ptr; line_ptr++)
1790 if (*line_ptr == ' ' || *line_ptr == '\t')
1792 *line_ptr = '\0'; /* terminate token string */
1793 value = line_ptr + 1; /* set beginning of value */
1795 token_value_separator_found = TRUE;
1801 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1802 if (token_value_separator_found)
1804 if (!token_value_separator_warning)
1806 Error(ERR_INFO_LINE, "-");
1808 if (filename != NULL)
1810 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1811 Error(ERR_INFO, "- config file: '%s'", filename);
1815 Error(ERR_WARN, "missing token/value separator(s):");
1818 token_value_separator_warning = TRUE;
1821 if (filename != NULL)
1822 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1824 Error(ERR_INFO, "- line: '%s'", line_raw);
1830 /* cut trailing whitespaces from token */
1831 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1832 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1835 /* cut leading whitespaces from value */
1836 for (; *value; value++)
1837 if (*value != ' ' && *value != '\t')
1842 value = "true"; /* treat tokens without value as "true" */
1851 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1853 /* while the internal (old) interface does not require a token/value
1854 separator (for downwards compatibility with existing files which
1855 don't use them), it is mandatory for the external (new) interface */
1857 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1861 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1862 boolean top_recursion_level, boolean is_hash)
1864 static SetupFileHash *include_filename_hash = NULL;
1865 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1866 char *token, *value, *line_ptr;
1867 void *insert_ptr = NULL;
1868 boolean read_continued_line = FALSE;
1870 int line_nr = 0, token_count = 0, include_count = 0;
1872 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1873 token_value_separator_warning = FALSE;
1876 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1877 token_already_exists_warning = FALSE;
1880 if (!(file = fopen(filename, MODE_READ)))
1882 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1887 /* use "insert pointer" to store list end for constant insertion complexity */
1889 insert_ptr = setup_file_data;
1891 /* on top invocation, create hash to mark included files (to prevent loops) */
1892 if (top_recursion_level)
1893 include_filename_hash = newSetupFileHash();
1895 /* mark this file as already included (to prevent including it again) */
1896 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1900 /* read next line of input file */
1901 if (!fgets(line, MAX_LINE_LEN, file))
1904 /* check if line was completely read and is terminated by line break */
1905 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1908 /* cut trailing line break (this can be newline and/or carriage return) */
1909 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1910 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1913 /* copy raw input line for later use (mainly debugging output) */
1914 strcpy(line_raw, line);
1916 if (read_continued_line)
1919 /* !!! ??? WHY ??? !!! */
1920 /* cut leading whitespaces from input line */
1921 for (line_ptr = line; *line_ptr; line_ptr++)
1922 if (*line_ptr != ' ' && *line_ptr != '\t')
1926 /* append new line to existing line, if there is enough space */
1927 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1928 strcat(previous_line, line_ptr);
1930 strcpy(line, previous_line); /* copy storage buffer to line */
1932 read_continued_line = FALSE;
1935 /* if the last character is '\', continue at next line */
1936 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1938 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1939 strcpy(previous_line, line); /* copy line to storage buffer */
1941 read_continued_line = TRUE;
1946 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1947 line_raw, line_nr, FALSE))
1952 if (strEqual(token, "include"))
1954 if (getHashEntry(include_filename_hash, value) == NULL)
1956 char *basepath = getBasePath(filename);
1957 char *basename = getBaseName(value);
1958 char *filename_include = getPath2(basepath, basename);
1961 Error(ERR_INFO, "[including file '%s']", filename_include);
1964 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1968 free(filename_include);
1974 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1981 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1983 getHashEntry((SetupFileHash *)setup_file_data, token);
1985 if (old_value != NULL)
1987 if (!token_already_exists_warning)
1989 Error(ERR_INFO_LINE, "-");
1990 Error(ERR_WARN, "duplicate token(s) found in config file:");
1991 Error(ERR_INFO, "- config file: '%s'", filename);
1993 token_already_exists_warning = TRUE;
1996 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
1997 Error(ERR_INFO, " old value: '%s'", old_value);
1998 Error(ERR_INFO, " new value: '%s'", value);
2002 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2006 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2016 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2017 if (token_value_separator_warning)
2018 Error(ERR_INFO_LINE, "-");
2021 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2022 if (token_already_exists_warning)
2023 Error(ERR_INFO_LINE, "-");
2026 if (token_count == 0 && include_count == 0)
2027 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2029 if (top_recursion_level)
2030 freeSetupFileHash(include_filename_hash);
2037 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2038 boolean top_recursion_level, boolean is_hash)
2040 static SetupFileHash *include_filename_hash = NULL;
2041 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2042 char *token, *value, *line_ptr;
2043 void *insert_ptr = NULL;
2044 boolean read_continued_line = FALSE;
2047 int token_count = 0;
2049 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2050 token_value_separator_warning = FALSE;
2053 if (!(file = fopen(filename, MODE_READ)))
2055 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2060 /* use "insert pointer" to store list end for constant insertion complexity */
2062 insert_ptr = setup_file_data;
2064 /* on top invocation, create hash to mark included files (to prevent loops) */
2065 if (top_recursion_level)
2066 include_filename_hash = newSetupFileHash();
2068 /* mark this file as already included (to prevent including it again) */
2069 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2073 /* read next line of input file */
2074 if (!fgets(line, MAX_LINE_LEN, file))
2077 /* check if line was completely read and is terminated by line break */
2078 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2081 /* cut trailing line break (this can be newline and/or carriage return) */
2082 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2083 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2086 /* copy raw input line for later use (mainly debugging output) */
2087 strcpy(line_raw, line);
2089 if (read_continued_line)
2091 /* cut leading whitespaces from input line */
2092 for (line_ptr = line; *line_ptr; line_ptr++)
2093 if (*line_ptr != ' ' && *line_ptr != '\t')
2096 /* append new line to existing line, if there is enough space */
2097 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2098 strcat(previous_line, line_ptr);
2100 strcpy(line, previous_line); /* copy storage buffer to line */
2102 read_continued_line = FALSE;
2105 /* if the last character is '\', continue at next line */
2106 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2108 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2109 strcpy(previous_line, line); /* copy line to storage buffer */
2111 read_continued_line = TRUE;
2116 /* cut trailing comment from input line */
2117 for (line_ptr = line; *line_ptr; line_ptr++)
2119 if (*line_ptr == '#')
2126 /* cut trailing whitespaces from input line */
2127 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2128 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2131 /* ignore empty lines */
2135 /* cut leading whitespaces from token */
2136 for (token = line; *token; token++)
2137 if (*token != ' ' && *token != '\t')
2140 /* start with empty value as reliable default */
2143 token_value_separator_found = FALSE;
2145 /* find end of token to determine start of value */
2146 for (line_ptr = token; *line_ptr; line_ptr++)
2149 /* first look for an explicit token/value separator, like ':' or '=' */
2150 if (*line_ptr == ':' || *line_ptr == '=')
2152 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2155 *line_ptr = '\0'; /* terminate token string */
2156 value = line_ptr + 1; /* set beginning of value */
2158 token_value_separator_found = TRUE;
2164 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2165 /* fallback: if no token/value separator found, also allow whitespaces */
2166 if (!token_value_separator_found)
2168 for (line_ptr = token; *line_ptr; line_ptr++)
2170 if (*line_ptr == ' ' || *line_ptr == '\t')
2172 *line_ptr = '\0'; /* terminate token string */
2173 value = line_ptr + 1; /* set beginning of value */
2175 token_value_separator_found = TRUE;
2181 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2182 if (token_value_separator_found)
2184 if (!token_value_separator_warning)
2186 Error(ERR_INFO_LINE, "-");
2187 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2188 Error(ERR_INFO, "- config file: '%s'", filename);
2190 token_value_separator_warning = TRUE;
2193 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2199 /* cut trailing whitespaces from token */
2200 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2201 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2204 /* cut leading whitespaces from value */
2205 for (; *value; value++)
2206 if (*value != ' ' && *value != '\t')
2211 value = "true"; /* treat tokens without value as "true" */
2216 if (strEqual(token, "include"))
2218 if (getHashEntry(include_filename_hash, value) == NULL)
2220 char *basepath = getBasePath(filename);
2221 char *basename = getBaseName(value);
2222 char *filename_include = getPath2(basepath, basename);
2225 Error(ERR_INFO, "[including file '%s']", filename_include);
2228 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2232 free(filename_include);
2236 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2242 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2244 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2253 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2254 if (token_value_separator_warning)
2255 Error(ERR_INFO_LINE, "-");
2258 if (token_count == 0)
2259 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2261 if (top_recursion_level)
2262 freeSetupFileHash(include_filename_hash);
2268 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2272 if (!(file = fopen(filename, MODE_WRITE)))
2274 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2279 BEGIN_HASH_ITERATION(hash, itr)
2281 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2282 HASH_ITERATION_VALUE(itr)));
2284 END_HASH_ITERATION(hash, itr)
2289 SetupFileList *loadSetupFileList(char *filename)
2291 SetupFileList *setup_file_list = newSetupFileList("", "");
2292 SetupFileList *first_valid_list_entry;
2294 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2296 freeSetupFileList(setup_file_list);
2301 first_valid_list_entry = setup_file_list->next;
2303 /* free empty list header */
2304 setup_file_list->next = NULL;
2305 freeSetupFileList(setup_file_list);
2307 return first_valid_list_entry;
2310 SetupFileHash *loadSetupFileHash(char *filename)
2312 SetupFileHash *setup_file_hash = newSetupFileHash();
2314 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2316 freeSetupFileHash(setup_file_hash);
2321 return setup_file_hash;
2324 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2325 char *filename, char *identifier)
2327 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2330 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2331 else if (!checkCookieString(value, identifier))
2332 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2336 /* ========================================================================= */
2337 /* setup file stuff */
2338 /* ========================================================================= */
2340 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2341 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2342 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2344 /* level directory info */
2345 #define LEVELINFO_TOKEN_IDENTIFIER 0
2346 #define LEVELINFO_TOKEN_NAME 1
2347 #define LEVELINFO_TOKEN_NAME_SORTING 2
2348 #define LEVELINFO_TOKEN_AUTHOR 3
2349 #define LEVELINFO_TOKEN_YEAR 4
2350 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2351 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2352 #define LEVELINFO_TOKEN_TESTED_BY 7
2353 #define LEVELINFO_TOKEN_LEVELS 8
2354 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2355 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2356 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2357 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2358 #define LEVELINFO_TOKEN_READONLY 13
2359 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2360 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2361 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2362 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2363 #define LEVELINFO_TOKEN_MUSIC_SET 18
2364 #define LEVELINFO_TOKEN_FILENAME 19
2365 #define LEVELINFO_TOKEN_FILETYPE 20
2366 #define LEVELINFO_TOKEN_HANDICAP 21
2367 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2369 #define NUM_LEVELINFO_TOKENS 23
2371 static LevelDirTree ldi;
2373 static struct TokenInfo levelinfo_tokens[] =
2375 /* level directory info */
2376 { TYPE_STRING, &ldi.identifier, "identifier" },
2377 { TYPE_STRING, &ldi.name, "name" },
2378 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2379 { TYPE_STRING, &ldi.author, "author" },
2380 { TYPE_STRING, &ldi.year, "year" },
2381 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2382 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2383 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2384 { TYPE_INTEGER, &ldi.levels, "levels" },
2385 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2386 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2387 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2388 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2389 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2390 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2391 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2392 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2393 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2394 { TYPE_STRING, &ldi.music_set, "music_set" },
2395 { TYPE_STRING, &ldi.level_filename, "filename" },
2396 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2397 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2398 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2401 static struct TokenInfo artworkinfo_tokens[] =
2403 /* artwork directory info */
2404 { TYPE_STRING, &ldi.identifier, "identifier" },
2405 { TYPE_STRING, &ldi.subdir, "subdir" },
2406 { TYPE_STRING, &ldi.name, "name" },
2407 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2408 { TYPE_STRING, &ldi.author, "author" },
2409 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2410 { TYPE_STRING, &ldi.basepath, "basepath" },
2411 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2412 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2413 { TYPE_INTEGER, &ldi.color, "color" },
2414 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2419 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2423 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2424 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2425 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2426 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2429 ti->node_parent = NULL;
2430 ti->node_group = NULL;
2437 ti->fullpath = NULL;
2438 ti->basepath = NULL;
2439 ti->identifier = NULL;
2440 ti->name = getStringCopy(ANONYMOUS_NAME);
2441 ti->name_sorting = NULL;
2442 ti->author = getStringCopy(ANONYMOUS_NAME);
2445 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2446 ti->latest_engine = FALSE; /* default: get from level */
2447 ti->parent_link = FALSE;
2448 ti->in_user_dir = FALSE;
2449 ti->user_defined = FALSE;
2451 ti->class_desc = NULL;
2453 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2455 if (ti->type == TREE_TYPE_LEVEL_DIR)
2457 ti->imported_from = NULL;
2458 ti->imported_by = NULL;
2459 ti->tested_by = NULL;
2461 ti->graphics_set_ecs = NULL;
2462 ti->graphics_set_aga = NULL;
2463 ti->graphics_set = NULL;
2464 ti->sounds_set = NULL;
2465 ti->music_set = NULL;
2466 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2467 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2468 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2470 ti->level_filename = NULL;
2471 ti->level_filetype = NULL;
2474 ti->first_level = 0;
2476 ti->level_group = FALSE;
2477 ti->handicap_level = 0;
2478 ti->readonly = TRUE;
2479 ti->handicap = TRUE;
2480 ti->skip_levels = FALSE;
2484 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2488 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2490 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2495 /* copy all values from the parent structure */
2497 ti->type = parent->type;
2499 ti->node_top = parent->node_top;
2500 ti->node_parent = parent;
2501 ti->node_group = NULL;
2508 ti->fullpath = NULL;
2509 ti->basepath = NULL;
2510 ti->identifier = NULL;
2511 ti->name = getStringCopy(ANONYMOUS_NAME);
2512 ti->name_sorting = NULL;
2513 ti->author = getStringCopy(parent->author);
2514 ti->year = getStringCopy(parent->year);
2516 ti->sort_priority = parent->sort_priority;
2517 ti->latest_engine = parent->latest_engine;
2518 ti->parent_link = FALSE;
2519 ti->in_user_dir = parent->in_user_dir;
2520 ti->user_defined = parent->user_defined;
2521 ti->color = parent->color;
2522 ti->class_desc = getStringCopy(parent->class_desc);
2524 ti->infotext = getStringCopy(parent->infotext);
2526 if (ti->type == TREE_TYPE_LEVEL_DIR)
2528 ti->imported_from = getStringCopy(parent->imported_from);
2529 ti->imported_by = getStringCopy(parent->imported_by);
2530 ti->tested_by = getStringCopy(parent->tested_by);
2532 ti->graphics_set_ecs = NULL;
2533 ti->graphics_set_aga = NULL;
2534 ti->graphics_set = NULL;
2535 ti->sounds_set = NULL;
2536 ti->music_set = NULL;
2537 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2538 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2539 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2541 ti->level_filename = NULL;
2542 ti->level_filetype = NULL;
2545 ti->first_level = 0;
2547 ti->level_group = FALSE;
2548 ti->handicap_level = 0;
2549 ti->readonly = TRUE;
2550 ti->handicap = TRUE;
2551 ti->skip_levels = FALSE;
2555 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2557 TreeInfo *ti_copy = newTreeInfo();
2559 /* copy all values from the original structure */
2561 ti_copy->type = ti->type;
2563 ti_copy->node_top = ti->node_top;
2564 ti_copy->node_parent = ti->node_parent;
2565 ti_copy->node_group = ti->node_group;
2566 ti_copy->next = ti->next;
2568 ti_copy->cl_first = ti->cl_first;
2569 ti_copy->cl_cursor = ti->cl_cursor;
2571 ti_copy->subdir = getStringCopy(ti->subdir);
2572 ti_copy->fullpath = getStringCopy(ti->fullpath);
2573 ti_copy->basepath = getStringCopy(ti->basepath);
2574 ti_copy->identifier = getStringCopy(ti->identifier);
2575 ti_copy->name = getStringCopy(ti->name);
2576 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2577 ti_copy->author = getStringCopy(ti->author);
2578 ti_copy->year = getStringCopy(ti->year);
2579 ti_copy->imported_from = getStringCopy(ti->imported_from);
2580 ti_copy->imported_by = getStringCopy(ti->imported_by);
2581 ti_copy->tested_by = getStringCopy(ti->tested_by);
2583 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2584 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2585 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2586 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2587 ti_copy->music_set = getStringCopy(ti->music_set);
2588 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2589 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2590 ti_copy->music_path = getStringCopy(ti->music_path);
2592 ti_copy->level_filename = getStringCopy(ti->level_filename);
2593 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2595 ti_copy->levels = ti->levels;
2596 ti_copy->first_level = ti->first_level;
2597 ti_copy->last_level = ti->last_level;
2598 ti_copy->sort_priority = ti->sort_priority;
2600 ti_copy->latest_engine = ti->latest_engine;
2602 ti_copy->level_group = ti->level_group;
2603 ti_copy->parent_link = ti->parent_link;
2604 ti_copy->in_user_dir = ti->in_user_dir;
2605 ti_copy->user_defined = ti->user_defined;
2606 ti_copy->readonly = ti->readonly;
2607 ti_copy->handicap = ti->handicap;
2608 ti_copy->skip_levels = ti->skip_levels;
2610 ti_copy->color = ti->color;
2611 ti_copy->class_desc = getStringCopy(ti->class_desc);
2612 ti_copy->handicap_level = ti->handicap_level;
2614 ti_copy->infotext = getStringCopy(ti->infotext);
2619 static void freeTreeInfo(TreeInfo *ti)
2624 checked_free(ti->subdir);
2625 checked_free(ti->fullpath);
2626 checked_free(ti->basepath);
2627 checked_free(ti->identifier);
2629 checked_free(ti->name);
2630 checked_free(ti->name_sorting);
2631 checked_free(ti->author);
2632 checked_free(ti->year);
2634 checked_free(ti->class_desc);
2636 checked_free(ti->infotext);
2638 if (ti->type == TREE_TYPE_LEVEL_DIR)
2640 checked_free(ti->imported_from);
2641 checked_free(ti->imported_by);
2642 checked_free(ti->tested_by);
2644 checked_free(ti->graphics_set_ecs);
2645 checked_free(ti->graphics_set_aga);
2646 checked_free(ti->graphics_set);
2647 checked_free(ti->sounds_set);
2648 checked_free(ti->music_set);
2650 checked_free(ti->graphics_path);
2651 checked_free(ti->sounds_path);
2652 checked_free(ti->music_path);
2654 checked_free(ti->level_filename);
2655 checked_free(ti->level_filetype);
2661 void setSetupInfo(struct TokenInfo *token_info,
2662 int token_nr, char *token_value)
2664 int token_type = token_info[token_nr].type;
2665 void *setup_value = token_info[token_nr].value;
2667 if (token_value == NULL)
2670 /* set setup field to corresponding token value */
2675 *(boolean *)setup_value = get_boolean_from_string(token_value);
2679 *(Key *)setup_value = getKeyFromKeyName(token_value);
2683 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2687 *(int *)setup_value = get_integer_from_string(token_value);
2691 checked_free(*(char **)setup_value);
2692 *(char **)setup_value = getStringCopy(token_value);
2700 static int compareTreeInfoEntries(const void *object1, const void *object2)
2702 const TreeInfo *entry1 = *((TreeInfo **)object1);
2703 const TreeInfo *entry2 = *((TreeInfo **)object2);
2704 int class_sorting1, class_sorting2;
2707 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2709 class_sorting1 = LEVELSORTING(entry1);
2710 class_sorting2 = LEVELSORTING(entry2);
2714 class_sorting1 = ARTWORKSORTING(entry1);
2715 class_sorting2 = ARTWORKSORTING(entry2);
2718 if (entry1->parent_link || entry2->parent_link)
2719 compare_result = (entry1->parent_link ? -1 : +1);
2720 else if (entry1->sort_priority == entry2->sort_priority)
2722 char *name1 = getStringToLower(entry1->name_sorting);
2723 char *name2 = getStringToLower(entry2->name_sorting);
2725 compare_result = strcmp(name1, name2);
2730 else if (class_sorting1 == class_sorting2)
2731 compare_result = entry1->sort_priority - entry2->sort_priority;
2733 compare_result = class_sorting1 - class_sorting2;
2735 return compare_result;
2738 static void createParentTreeInfoNode(TreeInfo *node_parent)
2742 if (node_parent == NULL)
2745 ti_new = newTreeInfo();
2746 setTreeInfoToDefaults(ti_new, node_parent->type);
2748 ti_new->node_parent = node_parent;
2749 ti_new->parent_link = TRUE;
2751 setString(&ti_new->identifier, node_parent->identifier);
2752 setString(&ti_new->name, ".. (parent directory)");
2753 setString(&ti_new->name_sorting, ti_new->name);
2755 setString(&ti_new->subdir, "..");
2756 setString(&ti_new->fullpath, node_parent->fullpath);
2758 ti_new->sort_priority = node_parent->sort_priority;
2759 ti_new->latest_engine = node_parent->latest_engine;
2761 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2763 pushTreeInfo(&node_parent->node_group, ti_new);
2767 /* -------------------------------------------------------------------------- */
2768 /* functions for handling level and custom artwork info cache */
2769 /* -------------------------------------------------------------------------- */
2771 static void LoadArtworkInfoCache()
2773 InitCacheDirectory();
2775 if (artworkinfo_cache_old == NULL)
2777 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2779 /* try to load artwork info hash from already existing cache file */
2780 artworkinfo_cache_old = loadSetupFileHash(filename);
2782 /* if no artwork info cache file was found, start with empty hash */
2783 if (artworkinfo_cache_old == NULL)
2784 artworkinfo_cache_old = newSetupFileHash();
2789 if (artworkinfo_cache_new == NULL)
2790 artworkinfo_cache_new = newSetupFileHash();
2793 static void SaveArtworkInfoCache()
2795 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2797 InitCacheDirectory();
2799 saveSetupFileHash(artworkinfo_cache_new, filename);
2804 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2806 static char *prefix = NULL;
2808 checked_free(prefix);
2810 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2815 /* (identical to above function, but separate string buffer needed -- nasty) */
2816 static char *getCacheToken(char *prefix, char *suffix)
2818 static char *token = NULL;
2820 checked_free(token);
2822 token = getStringCat2WithSeparator(prefix, suffix, ".");
2827 static char *getFileTimestamp(char *filename)
2829 struct stat file_status;
2831 if (stat(filename, &file_status) != 0) /* cannot stat file */
2832 return getStringCopy(i_to_a(0));
2834 return getStringCopy(i_to_a(file_status.st_mtime));
2837 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2839 struct stat file_status;
2841 if (timestamp_string == NULL)
2844 if (stat(filename, &file_status) != 0) /* cannot stat file */
2847 return (file_status.st_mtime != atoi(timestamp_string));
2850 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2852 char *identifier = level_node->subdir;
2853 char *type_string = ARTWORK_DIRECTORY(type);
2854 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2855 char *token_main = getCacheToken(token_prefix, "CACHED");
2856 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2857 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2858 TreeInfo *artwork_info = NULL;
2860 if (!use_artworkinfo_cache)
2867 artwork_info = newTreeInfo();
2868 setTreeInfoToDefaults(artwork_info, type);
2870 /* set all structure fields according to the token/value pairs */
2871 ldi = *artwork_info;
2872 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2874 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2875 char *value = getHashEntry(artworkinfo_cache_old, token);
2877 setSetupInfo(artworkinfo_tokens, i, value);
2879 /* check if cache entry for this item is invalid or incomplete */
2883 Error(ERR_WARN, "cache entry '%s' invalid", token);
2890 *artwork_info = ldi;
2895 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2896 LEVELINFO_FILENAME);
2897 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2898 ARTWORKINFO_FILENAME(type));
2900 /* check if corresponding "levelinfo.conf" file has changed */
2901 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2902 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2904 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2907 /* check if corresponding "<artworkinfo>.conf" file has changed */
2908 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2909 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2911 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2916 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2919 checked_free(filename_levelinfo);
2920 checked_free(filename_artworkinfo);
2923 if (!cached && artwork_info != NULL)
2925 freeTreeInfo(artwork_info);
2930 return artwork_info;
2933 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2934 LevelDirTree *level_node, int type)
2936 char *identifier = level_node->subdir;
2937 char *type_string = ARTWORK_DIRECTORY(type);
2938 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2939 char *token_main = getCacheToken(token_prefix, "CACHED");
2940 boolean set_cache_timestamps = TRUE;
2943 setHashEntry(artworkinfo_cache_new, token_main, "true");
2945 if (set_cache_timestamps)
2947 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2948 LEVELINFO_FILENAME);
2949 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2950 ARTWORKINFO_FILENAME(type));
2951 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2952 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2954 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2955 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2957 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2958 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2960 checked_free(filename_levelinfo);
2961 checked_free(filename_artworkinfo);
2962 checked_free(timestamp_levelinfo);
2963 checked_free(timestamp_artworkinfo);
2966 ldi = *artwork_info;
2967 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2969 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2970 char *value = getSetupValue(artworkinfo_tokens[i].type,
2971 artworkinfo_tokens[i].value);
2973 setHashEntry(artworkinfo_cache_new, token, value);
2978 /* -------------------------------------------------------------------------- */
2979 /* functions for loading level info and custom artwork info */
2980 /* -------------------------------------------------------------------------- */
2982 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2983 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2985 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2986 TreeInfo *node_parent,
2987 char *level_directory,
2988 char *directory_name)
2991 static unsigned long progress_delay = 0;
2992 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2994 char *directory_path = getPath2(level_directory, directory_name);
2995 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2996 SetupFileHash *setup_file_hash;
2997 LevelDirTree *leveldir_new = NULL;
3000 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3001 if (!options.debug && !fileExists(filename))
3003 free(directory_path);
3009 setup_file_hash = loadSetupFileHash(filename);
3011 if (setup_file_hash == NULL)
3013 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3015 free(directory_path);
3021 leveldir_new = newTreeInfo();
3024 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3026 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3028 leveldir_new->subdir = getStringCopy(directory_name);
3030 checkSetupFileHashIdentifier(setup_file_hash, filename,
3031 getCookie("LEVELINFO"));
3033 /* set all structure fields according to the token/value pairs */
3034 ldi = *leveldir_new;
3035 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3036 setSetupInfo(levelinfo_tokens, i,
3037 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3038 *leveldir_new = ldi;
3040 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3041 setString(&leveldir_new->name, leveldir_new->subdir);
3043 if (leveldir_new->identifier == NULL)
3044 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3046 if (leveldir_new->name_sorting == NULL)
3047 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3049 if (node_parent == NULL) /* top level group */
3051 leveldir_new->basepath = getStringCopy(level_directory);
3052 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3054 else /* sub level group */
3056 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3057 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3061 if (leveldir_new->levels < 1)
3062 leveldir_new->levels = 1;
3065 leveldir_new->last_level =
3066 leveldir_new->first_level + leveldir_new->levels - 1;
3068 leveldir_new->in_user_dir =
3069 (!strEqual(leveldir_new->basepath, options.level_directory));
3071 /* adjust some settings if user's private level directory was detected */
3072 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3073 leveldir_new->in_user_dir &&
3074 (strEqual(leveldir_new->subdir, getLoginName()) ||
3075 strEqual(leveldir_new->name, getLoginName()) ||
3076 strEqual(leveldir_new->author, getRealName())))
3078 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3079 leveldir_new->readonly = FALSE;
3082 leveldir_new->user_defined =
3083 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3085 leveldir_new->color = LEVELCOLOR(leveldir_new);
3087 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3089 leveldir_new->handicap_level = /* set handicap to default value */
3090 (leveldir_new->user_defined || !leveldir_new->handicap ?
3091 leveldir_new->last_level : leveldir_new->first_level);
3095 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3096 leveldir_new->level_group);
3098 if (leveldir_new->level_group ||
3099 DelayReached(&progress_delay, progress_delay_value))
3100 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3103 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3107 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3109 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3111 /* skip level sets without levels (which are probably artwork base sets) */
3113 freeSetupFileHash(setup_file_hash);
3114 free(directory_path);
3122 pushTreeInfo(node_first, leveldir_new);
3124 freeSetupFileHash(setup_file_hash);
3126 if (leveldir_new->level_group)
3128 /* create node to link back to current level directory */
3129 createParentTreeInfoNode(leveldir_new);
3131 /* recursively step into sub-directory and look for more level series */
3132 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3133 leveldir_new, directory_path);
3136 free(directory_path);
3142 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3143 TreeInfo *node_parent,
3144 char *level_directory)
3147 struct dirent *dir_entry;
3148 boolean valid_entry_found = FALSE;
3150 if ((dir = opendir(level_directory)) == NULL)
3152 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3156 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3158 struct stat file_status;
3159 char *directory_name = dir_entry->d_name;
3160 char *directory_path = getPath2(level_directory, directory_name);
3162 /* skip entries for current and parent directory */
3163 if (strEqual(directory_name, ".") ||
3164 strEqual(directory_name, ".."))
3166 free(directory_path);
3170 /* find out if directory entry is itself a directory */
3171 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3172 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3174 free(directory_path);
3178 free(directory_path);
3180 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3181 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3182 strEqual(directory_name, MUSIC_DIRECTORY))
3185 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3192 /* special case: top level directory may directly contain "levelinfo.conf" */
3193 if (node_parent == NULL && !valid_entry_found)
3195 /* check if this directory directly contains a file "levelinfo.conf" */
3196 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3197 level_directory, ".");
3200 if (!valid_entry_found)
3201 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3205 boolean AdjustGraphicsForEMC()
3207 boolean settings_changed = FALSE;
3209 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3210 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3212 return settings_changed;
3215 void LoadLevelInfo()
3217 InitUserLevelDirectory(getLoginName());
3219 DrawInitText("Loading level series", 120, FC_GREEN);
3221 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3222 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3224 /* after loading all level set information, clone the level directory tree
3225 and remove all level sets without levels (these may still contain artwork
3226 to be offered in the setup menu as "custom artwork", and are therefore
3227 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3228 leveldir_first_all = leveldir_first;
3229 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3231 AdjustGraphicsForEMC();
3233 /* before sorting, the first entries will be from the user directory */
3234 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3236 if (leveldir_first == NULL)
3237 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3239 sortTreeInfo(&leveldir_first);
3242 dumpTreeInfo(leveldir_first, 0);
3246 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3247 TreeInfo *node_parent,
3248 char *base_directory,
3249 char *directory_name, int type)
3251 char *directory_path = getPath2(base_directory, directory_name);
3252 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3253 SetupFileHash *setup_file_hash = NULL;
3254 TreeInfo *artwork_new = NULL;
3257 if (fileExists(filename))
3258 setup_file_hash = loadSetupFileHash(filename);
3260 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3263 struct dirent *dir_entry;
3264 boolean valid_file_found = FALSE;
3266 if ((dir = opendir(directory_path)) != NULL)
3268 while ((dir_entry = readdir(dir)) != NULL)
3270 char *entry_name = dir_entry->d_name;
3272 if (FileIsArtworkType(entry_name, type))
3274 valid_file_found = TRUE;
3282 if (!valid_file_found)
3284 if (!strEqual(directory_name, "."))
3285 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3287 free(directory_path);
3294 artwork_new = newTreeInfo();
3297 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3299 setTreeInfoToDefaults(artwork_new, type);
3301 artwork_new->subdir = getStringCopy(directory_name);
3303 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3306 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3309 /* set all structure fields according to the token/value pairs */
3311 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3312 setSetupInfo(levelinfo_tokens, i,
3313 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3316 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3317 setString(&artwork_new->name, artwork_new->subdir);
3319 if (artwork_new->identifier == NULL)
3320 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3322 if (artwork_new->name_sorting == NULL)
3323 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3326 if (node_parent == NULL) /* top level group */
3328 artwork_new->basepath = getStringCopy(base_directory);
3329 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3331 else /* sub level group */
3333 artwork_new->basepath = getStringCopy(node_parent->basepath);
3334 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3337 artwork_new->in_user_dir =
3338 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3340 /* (may use ".sort_priority" from "setup_file_hash" above) */
3341 artwork_new->color = ARTWORKCOLOR(artwork_new);
3343 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3345 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3347 if (strEqual(artwork_new->subdir, "."))
3349 if (artwork_new->user_defined)
3351 setString(&artwork_new->identifier, "private");
3352 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3356 setString(&artwork_new->identifier, "classic");
3357 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3360 /* set to new values after changing ".sort_priority" */
3361 artwork_new->color = ARTWORKCOLOR(artwork_new);
3363 setString(&artwork_new->class_desc,
3364 getLevelClassDescription(artwork_new));
3368 setString(&artwork_new->identifier, artwork_new->subdir);
3371 setString(&artwork_new->name, artwork_new->identifier);
3372 setString(&artwork_new->name_sorting, artwork_new->name);
3376 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3379 pushTreeInfo(node_first, artwork_new);
3381 freeSetupFileHash(setup_file_hash);
3383 free(directory_path);
3389 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3390 TreeInfo *node_parent,
3391 char *base_directory, int type)
3394 struct dirent *dir_entry;
3395 boolean valid_entry_found = FALSE;
3397 if ((dir = opendir(base_directory)) == NULL)
3399 /* display error if directory is main "options.graphics_directory" etc. */
3400 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3401 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3406 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3408 struct stat file_status;
3409 char *directory_name = dir_entry->d_name;
3410 char *directory_path = getPath2(base_directory, directory_name);
3412 /* skip directory entries for current and parent directory */
3413 if (strEqual(directory_name, ".") ||
3414 strEqual(directory_name, ".."))
3416 free(directory_path);
3420 /* skip directory entries which are not a directory or are not accessible */
3421 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3422 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3424 free(directory_path);
3428 free(directory_path);
3430 /* check if this directory contains artwork with or without config file */
3431 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3433 directory_name, type);
3438 /* check if this directory directly contains artwork itself */
3439 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3440 base_directory, ".",
3442 if (!valid_entry_found)
3443 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3447 static TreeInfo *getDummyArtworkInfo(int type)
3449 /* this is only needed when there is completely no artwork available */
3450 TreeInfo *artwork_new = newTreeInfo();
3452 setTreeInfoToDefaults(artwork_new, type);
3454 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3455 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3456 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3458 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3459 setString(&artwork_new->name, UNDEFINED_FILENAME);
3460 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3465 void LoadArtworkInfo()
3467 LoadArtworkInfoCache();
3469 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3471 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3472 options.graphics_directory,
3473 TREE_TYPE_GRAPHICS_DIR);
3474 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3475 getUserGraphicsDir(),
3476 TREE_TYPE_GRAPHICS_DIR);
3478 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3479 options.sounds_directory,
3480 TREE_TYPE_SOUNDS_DIR);
3481 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3483 TREE_TYPE_SOUNDS_DIR);
3485 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3486 options.music_directory,
3487 TREE_TYPE_MUSIC_DIR);
3488 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3490 TREE_TYPE_MUSIC_DIR);
3492 if (artwork.gfx_first == NULL)
3493 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3494 if (artwork.snd_first == NULL)
3495 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3496 if (artwork.mus_first == NULL)
3497 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3499 /* before sorting, the first entries will be from the user directory */
3500 artwork.gfx_current =
3501 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3502 if (artwork.gfx_current == NULL)
3503 artwork.gfx_current =
3504 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3505 if (artwork.gfx_current == NULL)
3506 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3508 artwork.snd_current =
3509 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3510 if (artwork.snd_current == NULL)
3511 artwork.snd_current =
3512 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3513 if (artwork.snd_current == NULL)
3514 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3516 artwork.mus_current =
3517 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3518 if (artwork.mus_current == NULL)
3519 artwork.mus_current =
3520 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3521 if (artwork.mus_current == NULL)
3522 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3524 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3525 artwork.snd_current_identifier = artwork.snd_current->identifier;
3526 artwork.mus_current_identifier = artwork.mus_current->identifier;
3529 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3530 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3531 printf("music set == %s\n\n", artwork.mus_current_identifier);
3534 sortTreeInfo(&artwork.gfx_first);
3535 sortTreeInfo(&artwork.snd_first);
3536 sortTreeInfo(&artwork.mus_first);
3539 dumpTreeInfo(artwork.gfx_first, 0);
3540 dumpTreeInfo(artwork.snd_first, 0);
3541 dumpTreeInfo(artwork.mus_first, 0);
3545 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3546 LevelDirTree *level_node)
3549 static unsigned long progress_delay = 0;
3550 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3552 int type = (*artwork_node)->type;
3554 /* recursively check all level directories for artwork sub-directories */
3558 /* check all tree entries for artwork, but skip parent link entries */
3559 if (!level_node->parent_link)
3561 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3562 boolean cached = (artwork_new != NULL);
3566 pushTreeInfo(artwork_node, artwork_new);
3570 TreeInfo *topnode_last = *artwork_node;
3571 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3572 ARTWORK_DIRECTORY(type));
3574 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3576 if (topnode_last != *artwork_node) /* check for newly added node */
3578 artwork_new = *artwork_node;
3580 setString(&artwork_new->identifier, level_node->subdir);
3581 setString(&artwork_new->name, level_node->name);
3582 setString(&artwork_new->name_sorting, level_node->name_sorting);
3584 artwork_new->sort_priority = level_node->sort_priority;
3585 artwork_new->color = LEVELCOLOR(artwork_new);
3591 /* insert artwork info (from old cache or filesystem) into new cache */
3592 if (artwork_new != NULL)
3593 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3597 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3598 level_node->level_group);
3600 if (level_node->level_group ||
3601 DelayReached(&progress_delay, progress_delay_value))
3602 DrawInitText(level_node->name, 150, FC_YELLOW);
3605 if (level_node->node_group != NULL)
3606 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3608 level_node = level_node->next;
3612 void LoadLevelArtworkInfo()
3614 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3616 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3617 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3618 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3620 SaveArtworkInfoCache();
3622 /* needed for reloading level artwork not known at ealier stage */
3624 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3626 artwork.gfx_current =
3627 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3628 if (artwork.gfx_current == NULL)
3629 artwork.gfx_current =
3630 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3631 if (artwork.gfx_current == NULL)
3632 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3635 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3637 artwork.snd_current =
3638 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3639 if (artwork.snd_current == NULL)
3640 artwork.snd_current =
3641 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3642 if (artwork.snd_current == NULL)
3643 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3646 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3648 artwork.mus_current =
3649 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3650 if (artwork.mus_current == NULL)
3651 artwork.mus_current =
3652 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3653 if (artwork.mus_current == NULL)
3654 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3657 sortTreeInfo(&artwork.gfx_first);
3658 sortTreeInfo(&artwork.snd_first);
3659 sortTreeInfo(&artwork.mus_first);
3662 dumpTreeInfo(artwork.gfx_first, 0);
3663 dumpTreeInfo(artwork.snd_first, 0);
3664 dumpTreeInfo(artwork.mus_first, 0);
3668 static void SaveUserLevelInfo()
3670 LevelDirTree *level_info;
3675 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3677 if (!(file = fopen(filename, MODE_WRITE)))
3679 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3684 level_info = newTreeInfo();
3686 /* always start with reliable default values */
3687 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3689 setString(&level_info->name, getLoginName());
3690 setString(&level_info->author, getRealName());
3691 level_info->levels = 100;
3692 level_info->first_level = 1;
3694 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3696 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3697 getCookie("LEVELINFO")));
3700 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3702 if (i == LEVELINFO_TOKEN_NAME ||
3703 i == LEVELINFO_TOKEN_AUTHOR ||
3704 i == LEVELINFO_TOKEN_LEVELS ||
3705 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3706 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3708 /* just to make things nicer :) */
3709 if (i == LEVELINFO_TOKEN_AUTHOR)
3710 fprintf(file, "\n");
3713 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3717 SetFilePermissions(filename, PERMS_PRIVATE);
3719 freeTreeInfo(level_info);
3723 char *getSetupValue(int type, void *value)
3725 static char value_string[MAX_LINE_LEN];
3733 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3737 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3741 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3745 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3749 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3753 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3757 sprintf(value_string, "%d", *(int *)value);
3761 if (*(char **)value == NULL)
3764 strcpy(value_string, *(char **)value);
3768 value_string[0] = '\0';
3772 if (type & TYPE_GHOSTED)
3773 strcpy(value_string, "n/a");
3775 return value_string;
3778 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3782 static char token_string[MAX_LINE_LEN];
3783 int token_type = token_info[token_nr].type;
3784 void *setup_value = token_info[token_nr].value;
3785 char *token_text = token_info[token_nr].text;
3786 char *value_string = getSetupValue(token_type, setup_value);
3788 /* build complete token string */
3789 sprintf(token_string, "%s%s", prefix, token_text);
3791 /* build setup entry line */
3792 line = getFormattedSetupEntry(token_string, value_string);
3794 if (token_type == TYPE_KEY_X11)
3796 Key key = *(Key *)setup_value;
3797 char *keyname = getKeyNameFromKey(key);
3799 /* add comment, if useful */
3800 if (!strEqual(keyname, "(undefined)") &&
3801 !strEqual(keyname, "(unknown)"))
3803 /* add at least one whitespace */
3805 for (i = strlen(line); i < token_comment_position; i++)
3809 strcat(line, keyname);
3816 void LoadLevelSetup_LastSeries()
3818 /* ----------------------------------------------------------------------- */
3819 /* ~/.<program>/levelsetup.conf */
3820 /* ----------------------------------------------------------------------- */
3822 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3823 SetupFileHash *level_setup_hash = NULL;
3825 /* always start with reliable default values */
3826 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3828 if ((level_setup_hash = loadSetupFileHash(filename)))
3830 char *last_level_series =
3831 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3833 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3835 if (leveldir_current == NULL)
3836 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3838 checkSetupFileHashIdentifier(level_setup_hash, filename,
3839 getCookie("LEVELSETUP"));
3841 freeSetupFileHash(level_setup_hash);
3844 Error(ERR_WARN, "using default setup values");
3849 void SaveLevelSetup_LastSeries()
3851 /* ----------------------------------------------------------------------- */
3852 /* ~/.<program>/levelsetup.conf */
3853 /* ----------------------------------------------------------------------- */
3855 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3856 char *level_subdir = leveldir_current->subdir;
3859 InitUserDataDirectory();
3861 if (!(file = fopen(filename, MODE_WRITE)))
3863 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3868 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3869 getCookie("LEVELSETUP")));
3870 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3875 SetFilePermissions(filename, PERMS_PRIVATE);
3880 static void checkSeriesInfo()
3882 static char *level_directory = NULL;
3884 struct dirent *dir_entry;
3886 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3888 level_directory = getPath2((leveldir_current->in_user_dir ?
3889 getUserLevelDir(NULL) :
3890 options.level_directory),
3891 leveldir_current->fullpath);
3893 if ((dir = opendir(level_directory)) == NULL)
3895 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3899 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3901 if (strlen(dir_entry->d_name) > 4 &&
3902 dir_entry->d_name[3] == '.' &&
3903 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3905 char levelnum_str[4];
3908 strncpy(levelnum_str, dir_entry->d_name, 3);
3909 levelnum_str[3] = '\0';
3911 levelnum_value = atoi(levelnum_str);
3914 if (levelnum_value < leveldir_current->first_level)
3916 Error(ERR_WARN, "additional level %d found", levelnum_value);
3917 leveldir_current->first_level = levelnum_value;
3919 else if (levelnum_value > leveldir_current->last_level)
3921 Error(ERR_WARN, "additional level %d found", levelnum_value);
3922 leveldir_current->last_level = levelnum_value;
3931 void LoadLevelSetup_SeriesInfo()
3934 SetupFileHash *level_setup_hash = NULL;
3935 char *level_subdir = leveldir_current->subdir;
3937 /* always start with reliable default values */
3938 level_nr = leveldir_current->first_level;
3940 checkSeriesInfo(leveldir_current);
3942 /* ----------------------------------------------------------------------- */
3943 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3944 /* ----------------------------------------------------------------------- */
3946 level_subdir = leveldir_current->subdir;
3948 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3950 if ((level_setup_hash = loadSetupFileHash(filename)))
3954 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3958 level_nr = atoi(token_value);
3960 if (level_nr < leveldir_current->first_level)
3961 level_nr = leveldir_current->first_level;
3962 if (level_nr > leveldir_current->last_level)
3963 level_nr = leveldir_current->last_level;
3966 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3970 int level_nr = atoi(token_value);
3972 if (level_nr < leveldir_current->first_level)
3973 level_nr = leveldir_current->first_level;
3974 if (level_nr > leveldir_current->last_level + 1)
3975 level_nr = leveldir_current->last_level;
3977 if (leveldir_current->user_defined || !leveldir_current->handicap)
3978 level_nr = leveldir_current->last_level;
3980 leveldir_current->handicap_level = level_nr;
3983 checkSetupFileHashIdentifier(level_setup_hash, filename,
3984 getCookie("LEVELSETUP"));
3986 freeSetupFileHash(level_setup_hash);
3989 Error(ERR_WARN, "using default setup values");
3994 void SaveLevelSetup_SeriesInfo()
3997 char *level_subdir = leveldir_current->subdir;
3998 char *level_nr_str = int2str(level_nr, 0);
3999 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4002 /* ----------------------------------------------------------------------- */
4003 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4004 /* ----------------------------------------------------------------------- */
4006 InitLevelSetupDirectory(level_subdir);
4008 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4010 if (!(file = fopen(filename, MODE_WRITE)))
4012 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4017 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4018 getCookie("LEVELSETUP")));
4019 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4021 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4022 handicap_level_str));
4026 SetFilePermissions(filename, PERMS_PRIVATE);