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);
266 static char *getDefaultArtworkSet(int type)
268 return (type == TREE_TYPE_GRAPHICS_DIR ? "gfx_classic" :
269 type == TREE_TYPE_SOUNDS_DIR ? "snd_classic" :
270 type == TREE_TYPE_MUSIC_DIR ? "mus_classic" : "");
273 static char *getDefaultArtworkDir(int type)
275 return (type == TREE_TYPE_GRAPHICS_DIR ?
276 getDefaultGraphicsDir("gfx_classic") :
277 type == TREE_TYPE_SOUNDS_DIR ?
278 getDefaultSoundsDir("snd_classic") :
279 type == TREE_TYPE_MUSIC_DIR ?
280 getDefaultMusicDir("mus_classic") : "");
285 static char *getDefaultArtworkSet(int type)
287 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
288 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
289 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
292 static char *getDefaultArtworkDir(int type)
294 return (type == TREE_TYPE_GRAPHICS_DIR ?
295 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
296 type == TREE_TYPE_SOUNDS_DIR ?
297 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
298 type == TREE_TYPE_MUSIC_DIR ?
299 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
303 static char *getUserGraphicsDir()
305 static char *usergraphics_dir = NULL;
307 if (usergraphics_dir == NULL)
308 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
310 return usergraphics_dir;
313 static char *getUserSoundsDir()
315 static char *usersounds_dir = NULL;
317 if (usersounds_dir == NULL)
318 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
320 return usersounds_dir;
323 static char *getUserMusicDir()
325 static char *usermusic_dir = NULL;
327 if (usermusic_dir == NULL)
328 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
330 return usermusic_dir;
333 static char *getSetupArtworkDir(TreeInfo *ti)
335 static char *artwork_dir = NULL;
337 checked_free(artwork_dir);
339 artwork_dir = getPath2(ti->basepath, ti->fullpath);
344 char *setLevelArtworkDir(TreeInfo *ti)
346 char **artwork_path_ptr, **artwork_set_ptr;
347 TreeInfo *level_artwork;
349 if (ti == NULL || leveldir_current == NULL)
352 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
353 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
355 checked_free(*artwork_path_ptr);
357 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
358 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
361 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
362 normally result in using the artwork configured in the setup menu. But
363 if an artwork subdirectory exists (which might contain custom artwork
364 or an artwork configuration file), this level artwork must be treated
365 as relative to the default "classic" artwork, not to the artwork that
366 is currently configured in the setup menu. */
368 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
370 checked_free(*artwork_set_ptr);
374 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
375 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
379 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
380 *artwork_set_ptr = NULL;
386 return *artwork_set_ptr;
389 inline static char *getLevelArtworkSet(int type)
391 if (leveldir_current == NULL)
394 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
397 inline static char *getLevelArtworkDir(int type)
399 if (leveldir_current == NULL)
400 return UNDEFINED_FILENAME;
402 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
405 char *getTapeFilename(int nr)
407 static char *filename = NULL;
408 char basename[MAX_FILENAME_LEN];
410 checked_free(filename);
412 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
413 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
418 char *getSolutionTapeFilename(int nr)
420 static char *filename = NULL;
421 char basename[MAX_FILENAME_LEN];
423 checked_free(filename);
425 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
426 filename = getPath2(getSolutionTapeDir(), basename);
431 char *getScoreFilename(int nr)
433 static char *filename = NULL;
434 char basename[MAX_FILENAME_LEN];
436 checked_free(filename);
438 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
439 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
444 char *getSetupFilename()
446 static char *filename = NULL;
448 checked_free(filename);
450 filename = getPath2(getSetupDir(), SETUP_FILENAME);
455 char *getEditorSetupFilename()
457 static char *filename = NULL;
459 checked_free(filename);
460 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
462 if (fileExists(filename))
465 checked_free(filename);
466 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
471 char *getHelpAnimFilename()
473 static char *filename = NULL;
475 checked_free(filename);
477 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
482 char *getHelpTextFilename()
484 static char *filename = NULL;
486 checked_free(filename);
488 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
493 char *getLevelSetInfoFilename()
495 static char *filename = NULL;
510 for (i = 0; basenames[i] != NULL; i++)
512 checked_free(filename);
513 filename = getPath2(getCurrentLevelDir(), basenames[i]);
515 if (fileExists(filename))
522 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
524 static char basename[32];
526 sprintf(basename, "%s_%d.txt",
527 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
532 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
534 static char *filename = NULL;
536 boolean skip_setup_artwork = FALSE;
538 checked_free(filename);
540 basename = getLevelSetTitleMessageBasename(nr, initial);
542 if (!gfx.override_level_graphics)
544 /* 1st try: look for special artwork in current level series directory */
545 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
546 if (fileExists(filename))
551 /* 2nd try: look for message file in current level set directory */
552 filename = getPath2(getCurrentLevelDir(), basename);
553 if (fileExists(filename))
558 /* check if there is special artwork configured in level series config */
559 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
561 /* 3rd try: look for special artwork configured in level series config */
562 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
563 if (fileExists(filename))
568 /* take missing artwork configured in level set config from default */
569 skip_setup_artwork = TRUE;
573 if (!skip_setup_artwork)
575 /* 4th try: look for special artwork in configured artwork directory */
576 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
577 if (fileExists(filename))
583 /* 5th try: look for default artwork in new default artwork directory */
584 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
585 if (fileExists(filename))
590 /* 6th try: look for default artwork in old default artwork directory */
591 filename = getPath2(options.graphics_directory, basename);
592 if (fileExists(filename))
595 return NULL; /* cannot find specified artwork file anywhere */
598 static char *getCorrectedArtworkBasename(char *basename)
600 char *basename_corrected = basename;
602 #if defined(PLATFORM_MSDOS)
603 if (program.filename_prefix != NULL)
605 int prefix_len = strlen(program.filename_prefix);
607 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
608 basename_corrected = &basename[prefix_len];
610 /* if corrected filename is still longer than standard MS-DOS filename
611 size (8 characters + 1 dot + 3 characters file extension), shorten
612 filename by writing file extension after 8th basename character */
613 if (strlen(basename_corrected) > 8 + 1 + 3)
615 static char *msdos_filename = NULL;
617 checked_free(msdos_filename);
619 msdos_filename = getStringCopy(basename_corrected);
620 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
622 basename_corrected = msdos_filename;
627 return basename_corrected;
630 char *getCustomImageFilename(char *basename)
632 static char *filename = NULL;
633 boolean skip_setup_artwork = FALSE;
635 checked_free(filename);
637 basename = getCorrectedArtworkBasename(basename);
639 if (!gfx.override_level_graphics)
641 /* 1st try: look for special artwork in current level series directory */
642 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
643 if (fileExists(filename))
648 /* check if there is special artwork configured in level series config */
649 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
651 /* 2nd try: look for special artwork configured in level series config */
652 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
653 if (fileExists(filename))
658 /* take missing artwork configured in level set config from default */
659 skip_setup_artwork = TRUE;
663 if (!skip_setup_artwork)
665 /* 3rd try: look for special artwork in configured artwork directory */
666 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
667 if (fileExists(filename))
673 /* 4th try: look for default artwork in new default artwork directory */
674 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
675 if (fileExists(filename))
680 /* 5th try: look for default artwork in old default artwork directory */
681 filename = getPath2(options.graphics_directory, basename);
682 if (fileExists(filename))
685 #if CREATE_SPECIAL_EDITION
688 /* !!! INSERT WARNING HERE TO REPORT MISSING ARTWORK FILES !!! */
690 printf("::: MISSING ARTWORK FILE '%s'\n", basename);
693 /* 6th try: look for fallback artwork in old default artwork directory */
694 /* (needed to prevent errors when trying to access unused artwork files) */
695 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
696 if (fileExists(filename))
700 return NULL; /* cannot find specified artwork file anywhere */
703 char *getCustomSoundFilename(char *basename)
705 static char *filename = NULL;
706 boolean skip_setup_artwork = FALSE;
708 checked_free(filename);
710 basename = getCorrectedArtworkBasename(basename);
712 if (!gfx.override_level_sounds)
714 /* 1st try: look for special artwork in current level series directory */
715 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
716 if (fileExists(filename))
721 /* check if there is special artwork configured in level series config */
722 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
724 /* 2nd try: look for special artwork configured in level series config */
725 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
726 if (fileExists(filename))
731 /* take missing artwork configured in level set config from default */
732 skip_setup_artwork = TRUE;
736 if (!skip_setup_artwork)
738 /* 3rd try: look for special artwork in configured artwork directory */
739 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
740 if (fileExists(filename))
746 /* 4th try: look for default artwork in new default artwork directory */
747 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
748 if (fileExists(filename))
753 /* 5th try: look for default artwork in old default artwork directory */
754 filename = getPath2(options.sounds_directory, basename);
755 if (fileExists(filename))
758 #if CREATE_SPECIAL_EDITION
761 /* 6th try: look for fallback artwork in old default artwork directory */
762 /* (needed to prevent errors when trying to access unused artwork files) */
763 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
764 if (fileExists(filename))
768 return NULL; /* cannot find specified artwork file anywhere */
771 char *getCustomMusicFilename(char *basename)
773 static char *filename = NULL;
774 boolean skip_setup_artwork = FALSE;
776 checked_free(filename);
778 basename = getCorrectedArtworkBasename(basename);
780 if (!gfx.override_level_music)
782 /* 1st try: look for special artwork in current level series directory */
783 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
784 if (fileExists(filename))
789 /* check if there is special artwork configured in level series config */
790 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
792 /* 2nd try: look for special artwork configured in level series config */
793 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
794 if (fileExists(filename))
799 /* take missing artwork configured in level set config from default */
800 skip_setup_artwork = TRUE;
804 if (!skip_setup_artwork)
806 /* 3rd try: look for special artwork in configured artwork directory */
807 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
808 if (fileExists(filename))
814 /* 4th try: look for default artwork in new default artwork directory */
815 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
816 if (fileExists(filename))
821 /* 5th try: look for default artwork in old default artwork directory */
822 filename = getPath2(options.music_directory, basename);
823 if (fileExists(filename))
826 #if CREATE_SPECIAL_EDITION
829 /* 6th try: look for fallback artwork in old default artwork directory */
830 /* (needed to prevent errors when trying to access unused artwork files) */
831 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
832 if (fileExists(filename))
836 return NULL; /* cannot find specified artwork file anywhere */
839 char *getCustomArtworkFilename(char *basename, int type)
841 if (type == ARTWORK_TYPE_GRAPHICS)
842 return getCustomImageFilename(basename);
843 else if (type == ARTWORK_TYPE_SOUNDS)
844 return getCustomSoundFilename(basename);
845 else if (type == ARTWORK_TYPE_MUSIC)
846 return getCustomMusicFilename(basename);
848 return UNDEFINED_FILENAME;
851 char *getCustomArtworkConfigFilename(int type)
853 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
856 char *getCustomArtworkLevelConfigFilename(int type)
858 static char *filename = NULL;
860 checked_free(filename);
862 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
867 char *getCustomMusicDirectory(void)
869 static char *directory = NULL;
870 boolean skip_setup_artwork = FALSE;
872 checked_free(directory);
874 if (!gfx.override_level_music)
876 /* 1st try: look for special artwork in current level series directory */
877 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
878 if (fileExists(directory))
883 /* check if there is special artwork configured in level series config */
884 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
886 /* 2nd try: look for special artwork configured in level series config */
887 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
888 if (fileExists(directory))
893 /* take missing artwork configured in level set config from default */
894 skip_setup_artwork = TRUE;
898 if (!skip_setup_artwork)
900 /* 3rd try: look for special artwork in configured artwork directory */
901 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
902 if (fileExists(directory))
908 /* 4th try: look for default artwork in new default artwork directory */
909 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
910 if (fileExists(directory))
915 /* 5th try: look for default artwork in old default artwork directory */
916 directory = getStringCopy(options.music_directory);
917 if (fileExists(directory))
920 return NULL; /* cannot find specified artwork file anywhere */
923 void InitTapeDirectory(char *level_subdir)
925 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
926 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
927 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
930 void InitScoreDirectory(char *level_subdir)
932 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
933 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
934 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
937 static void SaveUserLevelInfo();
939 void InitUserLevelDirectory(char *level_subdir)
941 if (!fileExists(getUserLevelDir(level_subdir)))
943 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
944 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
945 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
951 void InitLevelSetupDirectory(char *level_subdir)
953 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
954 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
955 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
958 void InitCacheDirectory()
960 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
961 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
965 /* ------------------------------------------------------------------------- */
966 /* some functions to handle lists of level and artwork directories */
967 /* ------------------------------------------------------------------------- */
969 TreeInfo *newTreeInfo()
971 return checked_calloc(sizeof(TreeInfo));
974 TreeInfo *newTreeInfo_setDefaults(int type)
976 TreeInfo *ti = newTreeInfo();
978 setTreeInfoToDefaults(ti, type);
983 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
985 node_new->next = *node_first;
986 *node_first = node_new;
989 int numTreeInfo(TreeInfo *node)
1002 boolean validLevelSeries(TreeInfo *node)
1004 return (node != NULL && !node->node_group && !node->parent_link);
1007 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1012 if (node->node_group) /* enter level group (step down into tree) */
1013 return getFirstValidTreeInfoEntry(node->node_group);
1014 else if (node->parent_link) /* skip start entry of level group */
1016 if (node->next) /* get first real level series entry */
1017 return getFirstValidTreeInfoEntry(node->next);
1018 else /* leave empty level group and go on */
1019 return getFirstValidTreeInfoEntry(node->node_parent->next);
1021 else /* this seems to be a regular level series */
1025 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1030 if (node->node_parent == NULL) /* top level group */
1031 return *node->node_top;
1032 else /* sub level group */
1033 return node->node_parent->node_group;
1036 int numTreeInfoInGroup(TreeInfo *node)
1038 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1041 int posTreeInfo(TreeInfo *node)
1043 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1048 if (node_cmp == node)
1052 node_cmp = node_cmp->next;
1058 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1060 TreeInfo *node_default = node;
1072 return node_default;
1075 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1077 if (identifier == NULL)
1082 if (node->node_group)
1084 TreeInfo *node_group;
1086 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1091 else if (!node->parent_link)
1093 if (strEqual(identifier, node->identifier))
1103 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1104 TreeInfo *node, boolean skip_sets_without_levels)
1111 if (!node->parent_link && !node->level_group &&
1112 skip_sets_without_levels && node->levels == 0)
1113 return cloneTreeNode(node_top, node_parent, node->next,
1114 skip_sets_without_levels);
1117 node_new = getTreeInfoCopy(node); /* copy complete node */
1119 node_new = newTreeInfo();
1121 *node_new = *node; /* copy complete node */
1124 node_new->node_top = node_top; /* correct top node link */
1125 node_new->node_parent = node_parent; /* correct parent node link */
1127 if (node->level_group)
1128 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1129 skip_sets_without_levels);
1131 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1132 skip_sets_without_levels);
1137 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1139 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1141 *ti_new = ti_cloned;
1144 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1146 boolean settings_changed = FALSE;
1150 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1151 !strEqual(node->graphics_set, node->graphics_set_ecs))
1153 setString(&node->graphics_set, node->graphics_set_ecs);
1154 settings_changed = TRUE;
1156 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1157 !strEqual(node->graphics_set, node->graphics_set_aga))
1159 setString(&node->graphics_set, node->graphics_set_aga);
1160 settings_changed = TRUE;
1163 if (node->node_group != NULL)
1164 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1169 return settings_changed;
1172 void dumpTreeInfo(TreeInfo *node, int depth)
1176 printf("Dumping TreeInfo:\n");
1180 for (i = 0; i < (depth + 1) * 3; i++)
1183 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1184 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1186 if (node->node_group != NULL)
1187 dumpTreeInfo(node->node_group, depth + 1);
1193 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1194 int (*compare_function)(const void *,
1197 int num_nodes = numTreeInfo(*node_first);
1198 TreeInfo **sort_array;
1199 TreeInfo *node = *node_first;
1205 /* allocate array for sorting structure pointers */
1206 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1208 /* writing structure pointers to sorting array */
1209 while (i < num_nodes && node) /* double boundary check... */
1211 sort_array[i] = node;
1217 /* sorting the structure pointers in the sorting array */
1218 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1221 /* update the linkage of list elements with the sorted node array */
1222 for (i = 0; i < num_nodes - 1; i++)
1223 sort_array[i]->next = sort_array[i + 1];
1224 sort_array[num_nodes - 1]->next = NULL;
1226 /* update the linkage of the main list anchor pointer */
1227 *node_first = sort_array[0];
1231 /* now recursively sort the level group structures */
1235 if (node->node_group != NULL)
1236 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1242 void sortTreeInfo(TreeInfo **node_first)
1244 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1248 /* ========================================================================= */
1249 /* some stuff from "files.c" */
1250 /* ========================================================================= */
1252 #if defined(PLATFORM_WIN32)
1254 #define S_IRGRP S_IRUSR
1257 #define S_IROTH S_IRUSR
1260 #define S_IWGRP S_IWUSR
1263 #define S_IWOTH S_IWUSR
1266 #define S_IXGRP S_IXUSR
1269 #define S_IXOTH S_IXUSR
1272 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1277 #endif /* PLATFORM_WIN32 */
1279 /* file permissions for newly written files */
1280 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1281 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1282 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1284 #define MODE_W_PRIVATE (S_IWUSR)
1285 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1286 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1288 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1289 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1291 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1292 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1296 static char *dir = NULL;
1298 #if defined(PLATFORM_WIN32)
1301 dir = checked_malloc(MAX_PATH + 1);
1303 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1306 #elif defined(PLATFORM_UNIX)
1309 if ((dir = getenv("HOME")) == NULL)
1313 if ((pwd = getpwuid(getuid())) != NULL)
1314 dir = getStringCopy(pwd->pw_dir);
1326 char *getCommonDataDir(void)
1328 static char *common_data_dir = NULL;
1330 #if defined(PLATFORM_WIN32)
1331 if (common_data_dir == NULL)
1333 char *dir = checked_malloc(MAX_PATH + 1);
1335 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1336 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1337 common_data_dir = getPath2(dir, program.userdata_subdir);
1339 common_data_dir = options.rw_base_directory;
1342 if (common_data_dir == NULL)
1343 common_data_dir = options.rw_base_directory;
1346 return common_data_dir;
1349 char *getPersonalDataDir(void)
1351 static char *personal_data_dir = NULL;
1353 #if defined(PLATFORM_MACOSX)
1354 if (personal_data_dir == NULL)
1355 personal_data_dir = getPath2(getHomeDir(), "Documents");
1357 if (personal_data_dir == NULL)
1358 personal_data_dir = getHomeDir();
1361 return personal_data_dir;
1364 char *getUserGameDataDir(void)
1366 static char *user_game_data_dir = NULL;
1368 if (user_game_data_dir == NULL)
1369 user_game_data_dir = getPath2(getPersonalDataDir(),
1370 program.userdata_subdir);
1372 return user_game_data_dir;
1375 void updateUserGameDataDir()
1377 #if defined(PLATFORM_MACOSX)
1378 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1379 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1381 /* convert old Unix style game data directory to Mac OS X style, if needed */
1382 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1384 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1386 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1387 userdata_dir_old, userdata_dir_new);
1389 /* continue using Unix style data directory -- this should not happen */
1390 program.userdata_path = getPath2(getPersonalDataDir(),
1391 program.userdata_subdir_unix);
1395 free(userdata_dir_old);
1401 return getUserGameDataDir();
1404 static mode_t posix_umask(mode_t mask)
1406 #if defined(PLATFORM_UNIX)
1413 static int posix_mkdir(const char *pathname, mode_t mode)
1415 #if defined(PLATFORM_WIN32)
1416 return mkdir(pathname);
1418 return mkdir(pathname, mode);
1422 void createDirectory(char *dir, char *text, int permission_class)
1424 /* leave "other" permissions in umask untouched, but ensure group parts
1425 of USERDATA_DIR_MODE are not masked */
1426 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1427 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1428 mode_t normal_umask = posix_umask(0);
1429 mode_t group_umask = ~(dir_mode & S_IRWXG);
1430 posix_umask(normal_umask & group_umask);
1432 if (!fileExists(dir))
1433 if (posix_mkdir(dir, dir_mode) != 0)
1434 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1436 posix_umask(normal_umask); /* reset normal umask */
1439 void InitUserDataDirectory()
1441 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1444 void SetFilePermissions(char *filename, int permission_class)
1446 chmod(filename, (permission_class == PERMS_PRIVATE ?
1447 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1450 char *getCookie(char *file_type)
1452 static char cookie[MAX_COOKIE_LEN + 1];
1454 if (strlen(program.cookie_prefix) + 1 +
1455 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1456 return "[COOKIE ERROR]"; /* should never happen */
1458 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1459 program.cookie_prefix, file_type,
1460 program.version_major, program.version_minor);
1465 int getFileVersionFromCookieString(const char *cookie)
1467 const char *ptr_cookie1, *ptr_cookie2;
1468 const char *pattern1 = "_FILE_VERSION_";
1469 const char *pattern2 = "?.?";
1470 const int len_cookie = strlen(cookie);
1471 const int len_pattern1 = strlen(pattern1);
1472 const int len_pattern2 = strlen(pattern2);
1473 const int len_pattern = len_pattern1 + len_pattern2;
1474 int version_major, version_minor;
1476 if (len_cookie <= len_pattern)
1479 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1480 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1482 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1485 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1486 ptr_cookie2[1] != '.' ||
1487 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1490 version_major = ptr_cookie2[0] - '0';
1491 version_minor = ptr_cookie2[2] - '0';
1493 return VERSION_IDENT(version_major, version_minor, 0, 0);
1496 boolean checkCookieString(const char *cookie, const char *template)
1498 const char *pattern = "_FILE_VERSION_?.?";
1499 const int len_cookie = strlen(cookie);
1500 const int len_template = strlen(template);
1501 const int len_pattern = strlen(pattern);
1503 if (len_cookie != len_template)
1506 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1512 /* ------------------------------------------------------------------------- */
1513 /* setup file list and hash handling functions */
1514 /* ------------------------------------------------------------------------- */
1516 char *getFormattedSetupEntry(char *token, char *value)
1519 static char entry[MAX_LINE_LEN];
1521 /* if value is an empty string, just return token without value */
1525 /* start with the token and some spaces to format output line */
1526 sprintf(entry, "%s:", token);
1527 for (i = strlen(entry); i < token_value_position; i++)
1530 /* continue with the token's value */
1531 strcat(entry, value);
1536 SetupFileList *newSetupFileList(char *token, char *value)
1538 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1540 new->token = getStringCopy(token);
1541 new->value = getStringCopy(value);
1548 void freeSetupFileList(SetupFileList *list)
1553 checked_free(list->token);
1554 checked_free(list->value);
1557 freeSetupFileList(list->next);
1562 char *getListEntry(SetupFileList *list, char *token)
1567 if (strEqual(list->token, token))
1570 return getListEntry(list->next, token);
1573 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1578 if (strEqual(list->token, token))
1580 checked_free(list->value);
1582 list->value = getStringCopy(value);
1586 else if (list->next == NULL)
1587 return (list->next = newSetupFileList(token, value));
1589 return setListEntry(list->next, token, value);
1592 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1597 if (list->next == NULL)
1598 return (list->next = newSetupFileList(token, value));
1600 return addListEntry(list->next, token, value);
1604 static void printSetupFileList(SetupFileList *list)
1609 printf("token: '%s'\n", list->token);
1610 printf("value: '%s'\n", list->value);
1612 printSetupFileList(list->next);
1617 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1618 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1619 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1620 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1622 #define insert_hash_entry hashtable_insert
1623 #define search_hash_entry hashtable_search
1624 #define change_hash_entry hashtable_change
1625 #define remove_hash_entry hashtable_remove
1628 static unsigned int get_hash_from_key(void *key)
1633 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1634 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1635 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1636 it works better than many other constants, prime or not) has never been
1637 adequately explained.
1639 If you just want to have a good hash function, and cannot wait, djb2
1640 is one of the best string hash functions i know. It has excellent
1641 distribution and speed on many different sets of keys and table sizes.
1642 You are not likely to do better with one of the "well known" functions
1643 such as PJW, K&R, etc.
1645 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1648 char *str = (char *)key;
1649 unsigned int hash = 5381;
1652 while ((c = *str++))
1653 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1658 static int keys_are_equal(void *key1, void *key2)
1660 return (strEqual((char *)key1, (char *)key2));
1663 SetupFileHash *newSetupFileHash()
1665 SetupFileHash *new_hash =
1666 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1668 if (new_hash == NULL)
1669 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1674 void freeSetupFileHash(SetupFileHash *hash)
1679 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1682 char *getHashEntry(SetupFileHash *hash, char *token)
1687 return search_hash_entry(hash, token);
1690 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1697 value_copy = getStringCopy(value);
1699 /* change value; if it does not exist, insert it as new */
1700 if (!change_hash_entry(hash, token, value_copy))
1701 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1702 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1705 char *removeHashEntry(SetupFileHash *hash, char *token)
1710 return remove_hash_entry(hash, token);
1714 static void printSetupFileHash(SetupFileHash *hash)
1716 BEGIN_HASH_ITERATION(hash, itr)
1718 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1719 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1721 END_HASH_ITERATION(hash, itr)
1725 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1726 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1727 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1729 static boolean token_value_separator_found = FALSE;
1730 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1731 static boolean token_value_separator_warning = FALSE;
1733 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1734 static boolean token_already_exists_warning = FALSE;
1737 static boolean getTokenValueFromSetupLineExt(char *line,
1738 char **token_ptr, char **value_ptr,
1739 char *filename, char *line_raw,
1741 boolean separator_required)
1743 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1744 char *token, *value, *line_ptr;
1746 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1747 if (line_raw == NULL)
1749 strncpy(line_copy, line, MAX_LINE_LEN);
1750 line_copy[MAX_LINE_LEN] = '\0';
1753 strcpy(line_raw_copy, line_copy);
1754 line_raw = line_raw_copy;
1757 /* cut trailing comment from input line */
1758 for (line_ptr = line; *line_ptr; line_ptr++)
1760 if (*line_ptr == '#')
1767 /* cut trailing whitespaces from input line */
1768 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1769 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1772 /* ignore empty lines */
1776 /* cut leading whitespaces from token */
1777 for (token = line; *token; token++)
1778 if (*token != ' ' && *token != '\t')
1781 /* start with empty value as reliable default */
1784 token_value_separator_found = FALSE;
1786 /* find end of token to determine start of value */
1787 for (line_ptr = token; *line_ptr; line_ptr++)
1790 /* first look for an explicit token/value separator, like ':' or '=' */
1791 if (*line_ptr == ':' || *line_ptr == '=')
1793 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1796 *line_ptr = '\0'; /* terminate token string */
1797 value = line_ptr + 1; /* set beginning of value */
1799 token_value_separator_found = TRUE;
1805 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1806 /* fallback: if no token/value separator found, also allow whitespaces */
1807 if (!token_value_separator_found && !separator_required)
1809 for (line_ptr = token; *line_ptr; line_ptr++)
1811 if (*line_ptr == ' ' || *line_ptr == '\t')
1813 *line_ptr = '\0'; /* terminate token string */
1814 value = line_ptr + 1; /* set beginning of value */
1816 token_value_separator_found = TRUE;
1822 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1823 if (token_value_separator_found)
1825 if (!token_value_separator_warning)
1827 Error(ERR_INFO_LINE, "-");
1829 if (filename != NULL)
1831 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1832 Error(ERR_INFO, "- config file: '%s'", filename);
1836 Error(ERR_WARN, "missing token/value separator(s):");
1839 token_value_separator_warning = TRUE;
1842 if (filename != NULL)
1843 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1845 Error(ERR_INFO, "- line: '%s'", line_raw);
1851 /* cut trailing whitespaces from token */
1852 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1853 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1856 /* cut leading whitespaces from value */
1857 for (; *value; value++)
1858 if (*value != ' ' && *value != '\t')
1863 value = "true"; /* treat tokens without value as "true" */
1872 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1874 /* while the internal (old) interface does not require a token/value
1875 separator (for downwards compatibility with existing files which
1876 don't use them), it is mandatory for the external (new) interface */
1878 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1882 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1883 boolean top_recursion_level, boolean is_hash)
1885 static SetupFileHash *include_filename_hash = NULL;
1886 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1887 char *token, *value, *line_ptr;
1888 void *insert_ptr = NULL;
1889 boolean read_continued_line = FALSE;
1891 int line_nr = 0, token_count = 0, include_count = 0;
1893 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1894 token_value_separator_warning = FALSE;
1897 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1898 token_already_exists_warning = FALSE;
1901 if (!(file = fopen(filename, MODE_READ)))
1903 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1908 /* use "insert pointer" to store list end for constant insertion complexity */
1910 insert_ptr = setup_file_data;
1912 /* on top invocation, create hash to mark included files (to prevent loops) */
1913 if (top_recursion_level)
1914 include_filename_hash = newSetupFileHash();
1916 /* mark this file as already included (to prevent including it again) */
1917 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1921 /* read next line of input file */
1922 if (!fgets(line, MAX_LINE_LEN, file))
1925 /* check if line was completely read and is terminated by line break */
1926 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1929 /* cut trailing line break (this can be newline and/or carriage return) */
1930 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1931 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1934 /* copy raw input line for later use (mainly debugging output) */
1935 strcpy(line_raw, line);
1937 if (read_continued_line)
1940 /* !!! ??? WHY ??? !!! */
1941 /* cut leading whitespaces from input line */
1942 for (line_ptr = line; *line_ptr; line_ptr++)
1943 if (*line_ptr != ' ' && *line_ptr != '\t')
1947 /* append new line to existing line, if there is enough space */
1948 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1949 strcat(previous_line, line_ptr);
1951 strcpy(line, previous_line); /* copy storage buffer to line */
1953 read_continued_line = FALSE;
1956 /* if the last character is '\', continue at next line */
1957 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1959 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1960 strcpy(previous_line, line); /* copy line to storage buffer */
1962 read_continued_line = TRUE;
1967 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1968 line_raw, line_nr, FALSE))
1973 if (strEqual(token, "include"))
1975 if (getHashEntry(include_filename_hash, value) == NULL)
1977 char *basepath = getBasePath(filename);
1978 char *basename = getBaseName(value);
1979 char *filename_include = getPath2(basepath, basename);
1982 Error(ERR_INFO, "[including file '%s']", filename_include);
1985 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1989 free(filename_include);
1995 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2002 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2004 getHashEntry((SetupFileHash *)setup_file_data, token);
2006 if (old_value != NULL)
2008 if (!token_already_exists_warning)
2010 Error(ERR_INFO_LINE, "-");
2011 Error(ERR_WARN, "duplicate token(s) found in config file:");
2012 Error(ERR_INFO, "- config file: '%s'", filename);
2014 token_already_exists_warning = TRUE;
2017 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2018 Error(ERR_INFO, " old value: '%s'", old_value);
2019 Error(ERR_INFO, " new value: '%s'", value);
2023 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2027 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2037 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2038 if (token_value_separator_warning)
2039 Error(ERR_INFO_LINE, "-");
2042 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2043 if (token_already_exists_warning)
2044 Error(ERR_INFO_LINE, "-");
2047 if (token_count == 0 && include_count == 0)
2048 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2050 if (top_recursion_level)
2051 freeSetupFileHash(include_filename_hash);
2058 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2059 boolean top_recursion_level, boolean is_hash)
2061 static SetupFileHash *include_filename_hash = NULL;
2062 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2063 char *token, *value, *line_ptr;
2064 void *insert_ptr = NULL;
2065 boolean read_continued_line = FALSE;
2068 int token_count = 0;
2070 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2071 token_value_separator_warning = FALSE;
2074 if (!(file = fopen(filename, MODE_READ)))
2076 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2081 /* use "insert pointer" to store list end for constant insertion complexity */
2083 insert_ptr = setup_file_data;
2085 /* on top invocation, create hash to mark included files (to prevent loops) */
2086 if (top_recursion_level)
2087 include_filename_hash = newSetupFileHash();
2089 /* mark this file as already included (to prevent including it again) */
2090 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2094 /* read next line of input file */
2095 if (!fgets(line, MAX_LINE_LEN, file))
2098 /* check if line was completely read and is terminated by line break */
2099 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2102 /* cut trailing line break (this can be newline and/or carriage return) */
2103 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2104 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2107 /* copy raw input line for later use (mainly debugging output) */
2108 strcpy(line_raw, line);
2110 if (read_continued_line)
2112 /* cut leading whitespaces from input line */
2113 for (line_ptr = line; *line_ptr; line_ptr++)
2114 if (*line_ptr != ' ' && *line_ptr != '\t')
2117 /* append new line to existing line, if there is enough space */
2118 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2119 strcat(previous_line, line_ptr);
2121 strcpy(line, previous_line); /* copy storage buffer to line */
2123 read_continued_line = FALSE;
2126 /* if the last character is '\', continue at next line */
2127 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2129 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2130 strcpy(previous_line, line); /* copy line to storage buffer */
2132 read_continued_line = TRUE;
2137 /* cut trailing comment from input line */
2138 for (line_ptr = line; *line_ptr; line_ptr++)
2140 if (*line_ptr == '#')
2147 /* cut trailing whitespaces from input line */
2148 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2149 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2152 /* ignore empty lines */
2156 /* cut leading whitespaces from token */
2157 for (token = line; *token; token++)
2158 if (*token != ' ' && *token != '\t')
2161 /* start with empty value as reliable default */
2164 token_value_separator_found = FALSE;
2166 /* find end of token to determine start of value */
2167 for (line_ptr = token; *line_ptr; line_ptr++)
2170 /* first look for an explicit token/value separator, like ':' or '=' */
2171 if (*line_ptr == ':' || *line_ptr == '=')
2173 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2176 *line_ptr = '\0'; /* terminate token string */
2177 value = line_ptr + 1; /* set beginning of value */
2179 token_value_separator_found = TRUE;
2185 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2186 /* fallback: if no token/value separator found, also allow whitespaces */
2187 if (!token_value_separator_found)
2189 for (line_ptr = token; *line_ptr; line_ptr++)
2191 if (*line_ptr == ' ' || *line_ptr == '\t')
2193 *line_ptr = '\0'; /* terminate token string */
2194 value = line_ptr + 1; /* set beginning of value */
2196 token_value_separator_found = TRUE;
2202 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2203 if (token_value_separator_found)
2205 if (!token_value_separator_warning)
2207 Error(ERR_INFO_LINE, "-");
2208 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2209 Error(ERR_INFO, "- config file: '%s'", filename);
2211 token_value_separator_warning = TRUE;
2214 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2220 /* cut trailing whitespaces from token */
2221 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2222 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2225 /* cut leading whitespaces from value */
2226 for (; *value; value++)
2227 if (*value != ' ' && *value != '\t')
2232 value = "true"; /* treat tokens without value as "true" */
2237 if (strEqual(token, "include"))
2239 if (getHashEntry(include_filename_hash, value) == NULL)
2241 char *basepath = getBasePath(filename);
2242 char *basename = getBaseName(value);
2243 char *filename_include = getPath2(basepath, basename);
2246 Error(ERR_INFO, "[including file '%s']", filename_include);
2249 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2253 free(filename_include);
2257 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2263 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2265 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2274 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2275 if (token_value_separator_warning)
2276 Error(ERR_INFO_LINE, "-");
2279 if (token_count == 0)
2280 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2282 if (top_recursion_level)
2283 freeSetupFileHash(include_filename_hash);
2289 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2293 if (!(file = fopen(filename, MODE_WRITE)))
2295 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2300 BEGIN_HASH_ITERATION(hash, itr)
2302 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2303 HASH_ITERATION_VALUE(itr)));
2305 END_HASH_ITERATION(hash, itr)
2310 SetupFileList *loadSetupFileList(char *filename)
2312 SetupFileList *setup_file_list = newSetupFileList("", "");
2313 SetupFileList *first_valid_list_entry;
2315 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2317 freeSetupFileList(setup_file_list);
2322 first_valid_list_entry = setup_file_list->next;
2324 /* free empty list header */
2325 setup_file_list->next = NULL;
2326 freeSetupFileList(setup_file_list);
2328 return first_valid_list_entry;
2331 SetupFileHash *loadSetupFileHash(char *filename)
2333 SetupFileHash *setup_file_hash = newSetupFileHash();
2335 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2337 freeSetupFileHash(setup_file_hash);
2342 return setup_file_hash;
2345 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2346 char *filename, char *identifier)
2348 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2351 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2352 else if (!checkCookieString(value, identifier))
2353 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2357 /* ========================================================================= */
2358 /* setup file stuff */
2359 /* ========================================================================= */
2361 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2362 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2363 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2365 /* level directory info */
2366 #define LEVELINFO_TOKEN_IDENTIFIER 0
2367 #define LEVELINFO_TOKEN_NAME 1
2368 #define LEVELINFO_TOKEN_NAME_SORTING 2
2369 #define LEVELINFO_TOKEN_AUTHOR 3
2370 #define LEVELINFO_TOKEN_YEAR 4
2371 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2372 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2373 #define LEVELINFO_TOKEN_TESTED_BY 7
2374 #define LEVELINFO_TOKEN_LEVELS 8
2375 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2376 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2377 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2378 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2379 #define LEVELINFO_TOKEN_READONLY 13
2380 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2381 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2382 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2383 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2384 #define LEVELINFO_TOKEN_MUSIC_SET 18
2385 #define LEVELINFO_TOKEN_FILENAME 19
2386 #define LEVELINFO_TOKEN_FILETYPE 20
2387 #define LEVELINFO_TOKEN_HANDICAP 21
2388 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2390 #define NUM_LEVELINFO_TOKENS 23
2392 static LevelDirTree ldi;
2394 static struct TokenInfo levelinfo_tokens[] =
2396 /* level directory info */
2397 { TYPE_STRING, &ldi.identifier, "identifier" },
2398 { TYPE_STRING, &ldi.name, "name" },
2399 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2400 { TYPE_STRING, &ldi.author, "author" },
2401 { TYPE_STRING, &ldi.year, "year" },
2402 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2403 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2404 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2405 { TYPE_INTEGER, &ldi.levels, "levels" },
2406 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2407 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2408 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2409 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2410 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2411 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2412 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2413 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2414 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2415 { TYPE_STRING, &ldi.music_set, "music_set" },
2416 { TYPE_STRING, &ldi.level_filename, "filename" },
2417 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2418 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2419 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2422 static struct TokenInfo artworkinfo_tokens[] =
2424 /* artwork directory info */
2425 { TYPE_STRING, &ldi.identifier, "identifier" },
2426 { TYPE_STRING, &ldi.subdir, "subdir" },
2427 { TYPE_STRING, &ldi.name, "name" },
2428 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2429 { TYPE_STRING, &ldi.author, "author" },
2430 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2431 { TYPE_STRING, &ldi.basepath, "basepath" },
2432 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2433 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2434 { TYPE_INTEGER, &ldi.color, "color" },
2435 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2440 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2444 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2445 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2446 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2447 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2450 ti->node_parent = NULL;
2451 ti->node_group = NULL;
2458 ti->fullpath = NULL;
2459 ti->basepath = NULL;
2460 ti->identifier = NULL;
2461 ti->name = getStringCopy(ANONYMOUS_NAME);
2462 ti->name_sorting = NULL;
2463 ti->author = getStringCopy(ANONYMOUS_NAME);
2466 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2467 ti->latest_engine = FALSE; /* default: get from level */
2468 ti->parent_link = FALSE;
2469 ti->in_user_dir = FALSE;
2470 ti->user_defined = FALSE;
2472 ti->class_desc = NULL;
2474 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2476 if (ti->type == TREE_TYPE_LEVEL_DIR)
2478 ti->imported_from = NULL;
2479 ti->imported_by = NULL;
2480 ti->tested_by = NULL;
2482 ti->graphics_set_ecs = NULL;
2483 ti->graphics_set_aga = NULL;
2484 ti->graphics_set = NULL;
2485 ti->sounds_set = NULL;
2486 ti->music_set = NULL;
2487 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2488 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2489 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2491 ti->level_filename = NULL;
2492 ti->level_filetype = NULL;
2495 ti->first_level = 0;
2497 ti->level_group = FALSE;
2498 ti->handicap_level = 0;
2499 ti->readonly = TRUE;
2500 ti->handicap = TRUE;
2501 ti->skip_levels = FALSE;
2505 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2509 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2511 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2516 /* copy all values from the parent structure */
2518 ti->type = parent->type;
2520 ti->node_top = parent->node_top;
2521 ti->node_parent = parent;
2522 ti->node_group = NULL;
2529 ti->fullpath = NULL;
2530 ti->basepath = NULL;
2531 ti->identifier = NULL;
2532 ti->name = getStringCopy(ANONYMOUS_NAME);
2533 ti->name_sorting = NULL;
2534 ti->author = getStringCopy(parent->author);
2535 ti->year = getStringCopy(parent->year);
2537 ti->sort_priority = parent->sort_priority;
2538 ti->latest_engine = parent->latest_engine;
2539 ti->parent_link = FALSE;
2540 ti->in_user_dir = parent->in_user_dir;
2541 ti->user_defined = parent->user_defined;
2542 ti->color = parent->color;
2543 ti->class_desc = getStringCopy(parent->class_desc);
2545 ti->infotext = getStringCopy(parent->infotext);
2547 if (ti->type == TREE_TYPE_LEVEL_DIR)
2549 ti->imported_from = getStringCopy(parent->imported_from);
2550 ti->imported_by = getStringCopy(parent->imported_by);
2551 ti->tested_by = getStringCopy(parent->tested_by);
2553 ti->graphics_set_ecs = NULL;
2554 ti->graphics_set_aga = NULL;
2555 ti->graphics_set = NULL;
2556 ti->sounds_set = NULL;
2557 ti->music_set = NULL;
2558 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2559 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2560 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2562 ti->level_filename = NULL;
2563 ti->level_filetype = NULL;
2566 ti->first_level = 0;
2568 ti->level_group = FALSE;
2569 ti->handicap_level = 0;
2570 ti->readonly = TRUE;
2571 ti->handicap = TRUE;
2572 ti->skip_levels = FALSE;
2576 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2578 TreeInfo *ti_copy = newTreeInfo();
2580 /* copy all values from the original structure */
2582 ti_copy->type = ti->type;
2584 ti_copy->node_top = ti->node_top;
2585 ti_copy->node_parent = ti->node_parent;
2586 ti_copy->node_group = ti->node_group;
2587 ti_copy->next = ti->next;
2589 ti_copy->cl_first = ti->cl_first;
2590 ti_copy->cl_cursor = ti->cl_cursor;
2592 ti_copy->subdir = getStringCopy(ti->subdir);
2593 ti_copy->fullpath = getStringCopy(ti->fullpath);
2594 ti_copy->basepath = getStringCopy(ti->basepath);
2595 ti_copy->identifier = getStringCopy(ti->identifier);
2596 ti_copy->name = getStringCopy(ti->name);
2597 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2598 ti_copy->author = getStringCopy(ti->author);
2599 ti_copy->year = getStringCopy(ti->year);
2600 ti_copy->imported_from = getStringCopy(ti->imported_from);
2601 ti_copy->imported_by = getStringCopy(ti->imported_by);
2602 ti_copy->tested_by = getStringCopy(ti->tested_by);
2604 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2605 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2606 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2607 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2608 ti_copy->music_set = getStringCopy(ti->music_set);
2609 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2610 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2611 ti_copy->music_path = getStringCopy(ti->music_path);
2613 ti_copy->level_filename = getStringCopy(ti->level_filename);
2614 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2616 ti_copy->levels = ti->levels;
2617 ti_copy->first_level = ti->first_level;
2618 ti_copy->last_level = ti->last_level;
2619 ti_copy->sort_priority = ti->sort_priority;
2621 ti_copy->latest_engine = ti->latest_engine;
2623 ti_copy->level_group = ti->level_group;
2624 ti_copy->parent_link = ti->parent_link;
2625 ti_copy->in_user_dir = ti->in_user_dir;
2626 ti_copy->user_defined = ti->user_defined;
2627 ti_copy->readonly = ti->readonly;
2628 ti_copy->handicap = ti->handicap;
2629 ti_copy->skip_levels = ti->skip_levels;
2631 ti_copy->color = ti->color;
2632 ti_copy->class_desc = getStringCopy(ti->class_desc);
2633 ti_copy->handicap_level = ti->handicap_level;
2635 ti_copy->infotext = getStringCopy(ti->infotext);
2640 static void freeTreeInfo(TreeInfo *ti)
2645 checked_free(ti->subdir);
2646 checked_free(ti->fullpath);
2647 checked_free(ti->basepath);
2648 checked_free(ti->identifier);
2650 checked_free(ti->name);
2651 checked_free(ti->name_sorting);
2652 checked_free(ti->author);
2653 checked_free(ti->year);
2655 checked_free(ti->class_desc);
2657 checked_free(ti->infotext);
2659 if (ti->type == TREE_TYPE_LEVEL_DIR)
2661 checked_free(ti->imported_from);
2662 checked_free(ti->imported_by);
2663 checked_free(ti->tested_by);
2665 checked_free(ti->graphics_set_ecs);
2666 checked_free(ti->graphics_set_aga);
2667 checked_free(ti->graphics_set);
2668 checked_free(ti->sounds_set);
2669 checked_free(ti->music_set);
2671 checked_free(ti->graphics_path);
2672 checked_free(ti->sounds_path);
2673 checked_free(ti->music_path);
2675 checked_free(ti->level_filename);
2676 checked_free(ti->level_filetype);
2682 void setSetupInfo(struct TokenInfo *token_info,
2683 int token_nr, char *token_value)
2685 int token_type = token_info[token_nr].type;
2686 void *setup_value = token_info[token_nr].value;
2688 if (token_value == NULL)
2691 /* set setup field to corresponding token value */
2696 *(boolean *)setup_value = get_boolean_from_string(token_value);
2700 *(Key *)setup_value = getKeyFromKeyName(token_value);
2704 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2708 *(int *)setup_value = get_integer_from_string(token_value);
2712 checked_free(*(char **)setup_value);
2713 *(char **)setup_value = getStringCopy(token_value);
2721 static int compareTreeInfoEntries(const void *object1, const void *object2)
2723 const TreeInfo *entry1 = *((TreeInfo **)object1);
2724 const TreeInfo *entry2 = *((TreeInfo **)object2);
2725 int class_sorting1, class_sorting2;
2728 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2730 class_sorting1 = LEVELSORTING(entry1);
2731 class_sorting2 = LEVELSORTING(entry2);
2735 class_sorting1 = ARTWORKSORTING(entry1);
2736 class_sorting2 = ARTWORKSORTING(entry2);
2739 if (entry1->parent_link || entry2->parent_link)
2740 compare_result = (entry1->parent_link ? -1 : +1);
2741 else if (entry1->sort_priority == entry2->sort_priority)
2743 char *name1 = getStringToLower(entry1->name_sorting);
2744 char *name2 = getStringToLower(entry2->name_sorting);
2746 compare_result = strcmp(name1, name2);
2751 else if (class_sorting1 == class_sorting2)
2752 compare_result = entry1->sort_priority - entry2->sort_priority;
2754 compare_result = class_sorting1 - class_sorting2;
2756 return compare_result;
2759 static void createParentTreeInfoNode(TreeInfo *node_parent)
2763 if (node_parent == NULL)
2766 ti_new = newTreeInfo();
2767 setTreeInfoToDefaults(ti_new, node_parent->type);
2769 ti_new->node_parent = node_parent;
2770 ti_new->parent_link = TRUE;
2772 setString(&ti_new->identifier, node_parent->identifier);
2773 setString(&ti_new->name, ".. (parent directory)");
2774 setString(&ti_new->name_sorting, ti_new->name);
2776 setString(&ti_new->subdir, "..");
2777 setString(&ti_new->fullpath, node_parent->fullpath);
2779 ti_new->sort_priority = node_parent->sort_priority;
2780 ti_new->latest_engine = node_parent->latest_engine;
2782 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2784 pushTreeInfo(&node_parent->node_group, ti_new);
2788 /* -------------------------------------------------------------------------- */
2789 /* functions for handling level and custom artwork info cache */
2790 /* -------------------------------------------------------------------------- */
2792 static void LoadArtworkInfoCache()
2794 InitCacheDirectory();
2796 if (artworkinfo_cache_old == NULL)
2798 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2800 /* try to load artwork info hash from already existing cache file */
2801 artworkinfo_cache_old = loadSetupFileHash(filename);
2803 /* if no artwork info cache file was found, start with empty hash */
2804 if (artworkinfo_cache_old == NULL)
2805 artworkinfo_cache_old = newSetupFileHash();
2810 if (artworkinfo_cache_new == NULL)
2811 artworkinfo_cache_new = newSetupFileHash();
2814 static void SaveArtworkInfoCache()
2816 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2818 InitCacheDirectory();
2820 saveSetupFileHash(artworkinfo_cache_new, filename);
2825 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2827 static char *prefix = NULL;
2829 checked_free(prefix);
2831 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2836 /* (identical to above function, but separate string buffer needed -- nasty) */
2837 static char *getCacheToken(char *prefix, char *suffix)
2839 static char *token = NULL;
2841 checked_free(token);
2843 token = getStringCat2WithSeparator(prefix, suffix, ".");
2848 static char *getFileTimestamp(char *filename)
2850 struct stat file_status;
2852 if (stat(filename, &file_status) != 0) /* cannot stat file */
2853 return getStringCopy(i_to_a(0));
2855 return getStringCopy(i_to_a(file_status.st_mtime));
2858 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2860 struct stat file_status;
2862 if (timestamp_string == NULL)
2865 if (stat(filename, &file_status) != 0) /* cannot stat file */
2868 return (file_status.st_mtime != atoi(timestamp_string));
2871 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2873 char *identifier = level_node->subdir;
2874 char *type_string = ARTWORK_DIRECTORY(type);
2875 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2876 char *token_main = getCacheToken(token_prefix, "CACHED");
2877 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2878 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2879 TreeInfo *artwork_info = NULL;
2881 if (!use_artworkinfo_cache)
2888 artwork_info = newTreeInfo();
2889 setTreeInfoToDefaults(artwork_info, type);
2891 /* set all structure fields according to the token/value pairs */
2892 ldi = *artwork_info;
2893 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2895 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2896 char *value = getHashEntry(artworkinfo_cache_old, token);
2898 setSetupInfo(artworkinfo_tokens, i, value);
2900 /* check if cache entry for this item is invalid or incomplete */
2904 Error(ERR_WARN, "cache entry '%s' invalid", token);
2911 *artwork_info = ldi;
2916 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2917 LEVELINFO_FILENAME);
2918 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2919 ARTWORKINFO_FILENAME(type));
2921 /* check if corresponding "levelinfo.conf" file has changed */
2922 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2923 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2925 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2928 /* check if corresponding "<artworkinfo>.conf" file has changed */
2929 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2930 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2932 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2937 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2940 checked_free(filename_levelinfo);
2941 checked_free(filename_artworkinfo);
2944 if (!cached && artwork_info != NULL)
2946 freeTreeInfo(artwork_info);
2951 return artwork_info;
2954 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2955 LevelDirTree *level_node, int type)
2957 char *identifier = level_node->subdir;
2958 char *type_string = ARTWORK_DIRECTORY(type);
2959 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2960 char *token_main = getCacheToken(token_prefix, "CACHED");
2961 boolean set_cache_timestamps = TRUE;
2964 setHashEntry(artworkinfo_cache_new, token_main, "true");
2966 if (set_cache_timestamps)
2968 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2969 LEVELINFO_FILENAME);
2970 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2971 ARTWORKINFO_FILENAME(type));
2972 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2973 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2975 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2976 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2978 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2979 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2981 checked_free(filename_levelinfo);
2982 checked_free(filename_artworkinfo);
2983 checked_free(timestamp_levelinfo);
2984 checked_free(timestamp_artworkinfo);
2987 ldi = *artwork_info;
2988 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2990 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2991 char *value = getSetupValue(artworkinfo_tokens[i].type,
2992 artworkinfo_tokens[i].value);
2994 setHashEntry(artworkinfo_cache_new, token, value);
2999 /* -------------------------------------------------------------------------- */
3000 /* functions for loading level info and custom artwork info */
3001 /* -------------------------------------------------------------------------- */
3003 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3004 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3006 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3007 TreeInfo *node_parent,
3008 char *level_directory,
3009 char *directory_name)
3012 static unsigned long progress_delay = 0;
3013 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3015 char *directory_path = getPath2(level_directory, directory_name);
3016 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3017 SetupFileHash *setup_file_hash;
3018 LevelDirTree *leveldir_new = NULL;
3021 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3022 if (!options.debug && !fileExists(filename))
3024 free(directory_path);
3030 setup_file_hash = loadSetupFileHash(filename);
3032 if (setup_file_hash == NULL)
3034 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3036 free(directory_path);
3042 leveldir_new = newTreeInfo();
3045 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3047 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3049 leveldir_new->subdir = getStringCopy(directory_name);
3051 checkSetupFileHashIdentifier(setup_file_hash, filename,
3052 getCookie("LEVELINFO"));
3054 /* set all structure fields according to the token/value pairs */
3055 ldi = *leveldir_new;
3056 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3057 setSetupInfo(levelinfo_tokens, i,
3058 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3059 *leveldir_new = ldi;
3061 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3062 setString(&leveldir_new->name, leveldir_new->subdir);
3064 if (leveldir_new->identifier == NULL)
3065 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3067 if (leveldir_new->name_sorting == NULL)
3068 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3070 if (node_parent == NULL) /* top level group */
3072 leveldir_new->basepath = getStringCopy(level_directory);
3073 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3075 else /* sub level group */
3077 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3078 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3082 if (leveldir_new->levels < 1)
3083 leveldir_new->levels = 1;
3086 leveldir_new->last_level =
3087 leveldir_new->first_level + leveldir_new->levels - 1;
3089 leveldir_new->in_user_dir =
3090 (!strEqual(leveldir_new->basepath, options.level_directory));
3092 /* adjust some settings if user's private level directory was detected */
3093 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3094 leveldir_new->in_user_dir &&
3095 (strEqual(leveldir_new->subdir, getLoginName()) ||
3096 strEqual(leveldir_new->name, getLoginName()) ||
3097 strEqual(leveldir_new->author, getRealName())))
3099 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3100 leveldir_new->readonly = FALSE;
3103 leveldir_new->user_defined =
3104 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3106 leveldir_new->color = LEVELCOLOR(leveldir_new);
3108 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3110 leveldir_new->handicap_level = /* set handicap to default value */
3111 (leveldir_new->user_defined || !leveldir_new->handicap ?
3112 leveldir_new->last_level : leveldir_new->first_level);
3116 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3117 leveldir_new->level_group);
3119 if (leveldir_new->level_group ||
3120 DelayReached(&progress_delay, progress_delay_value))
3121 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3124 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3128 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3130 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3132 /* skip level sets without levels (which are probably artwork base sets) */
3134 freeSetupFileHash(setup_file_hash);
3135 free(directory_path);
3143 pushTreeInfo(node_first, leveldir_new);
3145 freeSetupFileHash(setup_file_hash);
3147 if (leveldir_new->level_group)
3149 /* create node to link back to current level directory */
3150 createParentTreeInfoNode(leveldir_new);
3152 /* recursively step into sub-directory and look for more level series */
3153 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3154 leveldir_new, directory_path);
3157 free(directory_path);
3163 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3164 TreeInfo *node_parent,
3165 char *level_directory)
3168 struct dirent *dir_entry;
3169 boolean valid_entry_found = FALSE;
3171 if ((dir = opendir(level_directory)) == NULL)
3173 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3177 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3179 struct stat file_status;
3180 char *directory_name = dir_entry->d_name;
3181 char *directory_path = getPath2(level_directory, directory_name);
3183 /* skip entries for current and parent directory */
3184 if (strEqual(directory_name, ".") ||
3185 strEqual(directory_name, ".."))
3187 free(directory_path);
3191 /* find out if directory entry is itself a directory */
3192 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3193 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3195 free(directory_path);
3199 free(directory_path);
3201 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3202 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3203 strEqual(directory_name, MUSIC_DIRECTORY))
3206 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3213 /* special case: top level directory may directly contain "levelinfo.conf" */
3214 if (node_parent == NULL && !valid_entry_found)
3216 /* check if this directory directly contains a file "levelinfo.conf" */
3217 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3218 level_directory, ".");
3221 if (!valid_entry_found)
3222 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3226 boolean AdjustGraphicsForEMC()
3228 boolean settings_changed = FALSE;
3230 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3231 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3233 return settings_changed;
3236 void LoadLevelInfo()
3238 InitUserLevelDirectory(getLoginName());
3240 DrawInitText("Loading level series", 120, FC_GREEN);
3242 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3243 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3245 /* after loading all level set information, clone the level directory tree
3246 and remove all level sets without levels (these may still contain artwork
3247 to be offered in the setup menu as "custom artwork", and are therefore
3248 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3249 leveldir_first_all = leveldir_first;
3250 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3252 AdjustGraphicsForEMC();
3254 /* before sorting, the first entries will be from the user directory */
3255 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3257 if (leveldir_first == NULL)
3258 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3260 sortTreeInfo(&leveldir_first);
3263 dumpTreeInfo(leveldir_first, 0);
3267 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3268 TreeInfo *node_parent,
3269 char *base_directory,
3270 char *directory_name, int type)
3272 char *directory_path = getPath2(base_directory, directory_name);
3273 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3274 SetupFileHash *setup_file_hash = NULL;
3275 TreeInfo *artwork_new = NULL;
3278 if (fileExists(filename))
3279 setup_file_hash = loadSetupFileHash(filename);
3281 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3284 struct dirent *dir_entry;
3285 boolean valid_file_found = FALSE;
3287 if ((dir = opendir(directory_path)) != NULL)
3289 while ((dir_entry = readdir(dir)) != NULL)
3291 char *entry_name = dir_entry->d_name;
3293 if (FileIsArtworkType(entry_name, type))
3295 valid_file_found = TRUE;
3303 if (!valid_file_found)
3305 if (!strEqual(directory_name, "."))
3306 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3308 free(directory_path);
3315 artwork_new = newTreeInfo();
3318 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3320 setTreeInfoToDefaults(artwork_new, type);
3322 artwork_new->subdir = getStringCopy(directory_name);
3324 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3327 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3330 /* set all structure fields according to the token/value pairs */
3332 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3333 setSetupInfo(levelinfo_tokens, i,
3334 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3337 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3338 setString(&artwork_new->name, artwork_new->subdir);
3340 if (artwork_new->identifier == NULL)
3341 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3343 if (artwork_new->name_sorting == NULL)
3344 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3347 if (node_parent == NULL) /* top level group */
3349 artwork_new->basepath = getStringCopy(base_directory);
3350 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3352 else /* sub level group */
3354 artwork_new->basepath = getStringCopy(node_parent->basepath);
3355 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3358 artwork_new->in_user_dir =
3359 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3361 /* (may use ".sort_priority" from "setup_file_hash" above) */
3362 artwork_new->color = ARTWORKCOLOR(artwork_new);
3364 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3366 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3368 if (strEqual(artwork_new->subdir, "."))
3370 if (artwork_new->user_defined)
3372 setString(&artwork_new->identifier, "private");
3373 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3377 setString(&artwork_new->identifier, "classic");
3378 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3381 /* set to new values after changing ".sort_priority" */
3382 artwork_new->color = ARTWORKCOLOR(artwork_new);
3384 setString(&artwork_new->class_desc,
3385 getLevelClassDescription(artwork_new));
3389 setString(&artwork_new->identifier, artwork_new->subdir);
3392 setString(&artwork_new->name, artwork_new->identifier);
3393 setString(&artwork_new->name_sorting, artwork_new->name);
3397 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3400 pushTreeInfo(node_first, artwork_new);
3402 freeSetupFileHash(setup_file_hash);
3404 free(directory_path);
3410 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3411 TreeInfo *node_parent,
3412 char *base_directory, int type)
3415 struct dirent *dir_entry;
3416 boolean valid_entry_found = FALSE;
3418 if ((dir = opendir(base_directory)) == NULL)
3420 /* display error if directory is main "options.graphics_directory" etc. */
3421 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3422 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3427 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3429 struct stat file_status;
3430 char *directory_name = dir_entry->d_name;
3431 char *directory_path = getPath2(base_directory, directory_name);
3433 /* skip directory entries for current and parent directory */
3434 if (strEqual(directory_name, ".") ||
3435 strEqual(directory_name, ".."))
3437 free(directory_path);
3441 /* skip directory entries which are not a directory or are not accessible */
3442 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3443 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3445 free(directory_path);
3449 free(directory_path);
3451 /* check if this directory contains artwork with or without config file */
3452 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3454 directory_name, type);
3459 /* check if this directory directly contains artwork itself */
3460 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3461 base_directory, ".",
3463 if (!valid_entry_found)
3464 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3468 static TreeInfo *getDummyArtworkInfo(int type)
3470 /* this is only needed when there is completely no artwork available */
3471 TreeInfo *artwork_new = newTreeInfo();
3473 setTreeInfoToDefaults(artwork_new, type);
3475 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3476 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3477 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3479 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3480 setString(&artwork_new->name, UNDEFINED_FILENAME);
3481 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3486 void LoadArtworkInfo()
3488 LoadArtworkInfoCache();
3490 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3492 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3493 options.graphics_directory,
3494 TREE_TYPE_GRAPHICS_DIR);
3495 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3496 getUserGraphicsDir(),
3497 TREE_TYPE_GRAPHICS_DIR);
3499 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3500 options.sounds_directory,
3501 TREE_TYPE_SOUNDS_DIR);
3502 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3504 TREE_TYPE_SOUNDS_DIR);
3506 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3507 options.music_directory,
3508 TREE_TYPE_MUSIC_DIR);
3509 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3511 TREE_TYPE_MUSIC_DIR);
3513 if (artwork.gfx_first == NULL)
3514 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3515 if (artwork.snd_first == NULL)
3516 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3517 if (artwork.mus_first == NULL)
3518 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3520 /* before sorting, the first entries will be from the user directory */
3521 artwork.gfx_current =
3522 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3523 if (artwork.gfx_current == NULL)
3524 artwork.gfx_current =
3525 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3526 if (artwork.gfx_current == NULL)
3527 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3529 artwork.snd_current =
3530 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3531 if (artwork.snd_current == NULL)
3532 artwork.snd_current =
3533 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3534 if (artwork.snd_current == NULL)
3535 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3537 artwork.mus_current =
3538 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3539 if (artwork.mus_current == NULL)
3540 artwork.mus_current =
3541 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3542 if (artwork.mus_current == NULL)
3543 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3545 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3546 artwork.snd_current_identifier = artwork.snd_current->identifier;
3547 artwork.mus_current_identifier = artwork.mus_current->identifier;
3550 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3551 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3552 printf("music set == %s\n\n", artwork.mus_current_identifier);
3555 sortTreeInfo(&artwork.gfx_first);
3556 sortTreeInfo(&artwork.snd_first);
3557 sortTreeInfo(&artwork.mus_first);
3560 dumpTreeInfo(artwork.gfx_first, 0);
3561 dumpTreeInfo(artwork.snd_first, 0);
3562 dumpTreeInfo(artwork.mus_first, 0);
3566 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3567 LevelDirTree *level_node)
3570 static unsigned long progress_delay = 0;
3571 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3573 int type = (*artwork_node)->type;
3575 /* recursively check all level directories for artwork sub-directories */
3579 /* check all tree entries for artwork, but skip parent link entries */
3580 if (!level_node->parent_link)
3582 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3583 boolean cached = (artwork_new != NULL);
3587 pushTreeInfo(artwork_node, artwork_new);
3591 TreeInfo *topnode_last = *artwork_node;
3592 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3593 ARTWORK_DIRECTORY(type));
3595 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3597 if (topnode_last != *artwork_node) /* check for newly added node */
3599 artwork_new = *artwork_node;
3601 setString(&artwork_new->identifier, level_node->subdir);
3602 setString(&artwork_new->name, level_node->name);
3603 setString(&artwork_new->name_sorting, level_node->name_sorting);
3605 artwork_new->sort_priority = level_node->sort_priority;
3606 artwork_new->color = LEVELCOLOR(artwork_new);
3612 /* insert artwork info (from old cache or filesystem) into new cache */
3613 if (artwork_new != NULL)
3614 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3618 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3619 level_node->level_group);
3621 if (level_node->level_group ||
3622 DelayReached(&progress_delay, progress_delay_value))
3623 DrawInitText(level_node->name, 150, FC_YELLOW);
3626 if (level_node->node_group != NULL)
3627 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3629 level_node = level_node->next;
3633 void LoadLevelArtworkInfo()
3635 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3637 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3638 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3639 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3641 SaveArtworkInfoCache();
3643 /* needed for reloading level artwork not known at ealier stage */
3645 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3647 artwork.gfx_current =
3648 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3649 if (artwork.gfx_current == NULL)
3650 artwork.gfx_current =
3651 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3652 if (artwork.gfx_current == NULL)
3653 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3656 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3658 artwork.snd_current =
3659 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3660 if (artwork.snd_current == NULL)
3661 artwork.snd_current =
3662 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3663 if (artwork.snd_current == NULL)
3664 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3667 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3669 artwork.mus_current =
3670 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3671 if (artwork.mus_current == NULL)
3672 artwork.mus_current =
3673 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3674 if (artwork.mus_current == NULL)
3675 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3678 sortTreeInfo(&artwork.gfx_first);
3679 sortTreeInfo(&artwork.snd_first);
3680 sortTreeInfo(&artwork.mus_first);
3683 dumpTreeInfo(artwork.gfx_first, 0);
3684 dumpTreeInfo(artwork.snd_first, 0);
3685 dumpTreeInfo(artwork.mus_first, 0);
3689 static void SaveUserLevelInfo()
3691 LevelDirTree *level_info;
3696 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3698 if (!(file = fopen(filename, MODE_WRITE)))
3700 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3705 level_info = newTreeInfo();
3707 /* always start with reliable default values */
3708 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3710 setString(&level_info->name, getLoginName());
3711 setString(&level_info->author, getRealName());
3712 level_info->levels = 100;
3713 level_info->first_level = 1;
3715 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3717 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3718 getCookie("LEVELINFO")));
3721 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3723 if (i == LEVELINFO_TOKEN_NAME ||
3724 i == LEVELINFO_TOKEN_AUTHOR ||
3725 i == LEVELINFO_TOKEN_LEVELS ||
3726 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3727 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3729 /* just to make things nicer :) */
3730 if (i == LEVELINFO_TOKEN_AUTHOR)
3731 fprintf(file, "\n");
3734 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3738 SetFilePermissions(filename, PERMS_PRIVATE);
3740 freeTreeInfo(level_info);
3744 char *getSetupValue(int type, void *value)
3746 static char value_string[MAX_LINE_LEN];
3754 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3758 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3762 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3766 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3770 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3774 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3778 sprintf(value_string, "%d", *(int *)value);
3782 if (*(char **)value == NULL)
3785 strcpy(value_string, *(char **)value);
3789 value_string[0] = '\0';
3793 if (type & TYPE_GHOSTED)
3794 strcpy(value_string, "n/a");
3796 return value_string;
3799 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3803 static char token_string[MAX_LINE_LEN];
3804 int token_type = token_info[token_nr].type;
3805 void *setup_value = token_info[token_nr].value;
3806 char *token_text = token_info[token_nr].text;
3807 char *value_string = getSetupValue(token_type, setup_value);
3809 /* build complete token string */
3810 sprintf(token_string, "%s%s", prefix, token_text);
3812 /* build setup entry line */
3813 line = getFormattedSetupEntry(token_string, value_string);
3815 if (token_type == TYPE_KEY_X11)
3817 Key key = *(Key *)setup_value;
3818 char *keyname = getKeyNameFromKey(key);
3820 /* add comment, if useful */
3821 if (!strEqual(keyname, "(undefined)") &&
3822 !strEqual(keyname, "(unknown)"))
3824 /* add at least one whitespace */
3826 for (i = strlen(line); i < token_comment_position; i++)
3830 strcat(line, keyname);
3837 void LoadLevelSetup_LastSeries()
3839 /* ----------------------------------------------------------------------- */
3840 /* ~/.<program>/levelsetup.conf */
3841 /* ----------------------------------------------------------------------- */
3843 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3844 SetupFileHash *level_setup_hash = NULL;
3846 /* always start with reliable default values */
3847 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3849 #if CREATE_SPECIAL_EDITION_RND_JUE
3850 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3852 if (leveldir_current == NULL)
3853 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3856 if ((level_setup_hash = loadSetupFileHash(filename)))
3858 char *last_level_series =
3859 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3861 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3863 if (leveldir_current == NULL)
3864 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3866 checkSetupFileHashIdentifier(level_setup_hash, filename,
3867 getCookie("LEVELSETUP"));
3869 freeSetupFileHash(level_setup_hash);
3872 Error(ERR_WARN, "using default setup values");
3877 void SaveLevelSetup_LastSeries()
3879 /* ----------------------------------------------------------------------- */
3880 /* ~/.<program>/levelsetup.conf */
3881 /* ----------------------------------------------------------------------- */
3883 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3884 char *level_subdir = leveldir_current->subdir;
3887 InitUserDataDirectory();
3889 if (!(file = fopen(filename, MODE_WRITE)))
3891 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3896 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3897 getCookie("LEVELSETUP")));
3898 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3903 SetFilePermissions(filename, PERMS_PRIVATE);
3908 static void checkSeriesInfo()
3910 static char *level_directory = NULL;
3912 struct dirent *dir_entry;
3914 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3916 level_directory = getPath2((leveldir_current->in_user_dir ?
3917 getUserLevelDir(NULL) :
3918 options.level_directory),
3919 leveldir_current->fullpath);
3921 if ((dir = opendir(level_directory)) == NULL)
3923 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3927 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3929 if (strlen(dir_entry->d_name) > 4 &&
3930 dir_entry->d_name[3] == '.' &&
3931 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3933 char levelnum_str[4];
3936 strncpy(levelnum_str, dir_entry->d_name, 3);
3937 levelnum_str[3] = '\0';
3939 levelnum_value = atoi(levelnum_str);
3942 if (levelnum_value < leveldir_current->first_level)
3944 Error(ERR_WARN, "additional level %d found", levelnum_value);
3945 leveldir_current->first_level = levelnum_value;
3947 else if (levelnum_value > leveldir_current->last_level)
3949 Error(ERR_WARN, "additional level %d found", levelnum_value);
3950 leveldir_current->last_level = levelnum_value;
3959 void LoadLevelSetup_SeriesInfo()
3962 SetupFileHash *level_setup_hash = NULL;
3963 char *level_subdir = leveldir_current->subdir;
3965 /* always start with reliable default values */
3966 level_nr = leveldir_current->first_level;
3968 checkSeriesInfo(leveldir_current);
3970 /* ----------------------------------------------------------------------- */
3971 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3972 /* ----------------------------------------------------------------------- */
3974 level_subdir = leveldir_current->subdir;
3976 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3978 if ((level_setup_hash = loadSetupFileHash(filename)))
3982 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3986 level_nr = atoi(token_value);
3988 if (level_nr < leveldir_current->first_level)
3989 level_nr = leveldir_current->first_level;
3990 if (level_nr > leveldir_current->last_level)
3991 level_nr = leveldir_current->last_level;
3994 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3998 int level_nr = atoi(token_value);
4000 if (level_nr < leveldir_current->first_level)
4001 level_nr = leveldir_current->first_level;
4002 if (level_nr > leveldir_current->last_level + 1)
4003 level_nr = leveldir_current->last_level;
4005 if (leveldir_current->user_defined || !leveldir_current->handicap)
4006 level_nr = leveldir_current->last_level;
4008 leveldir_current->handicap_level = level_nr;
4011 checkSetupFileHashIdentifier(level_setup_hash, filename,
4012 getCookie("LEVELSETUP"));
4014 freeSetupFileHash(level_setup_hash);
4017 Error(ERR_WARN, "using default setup values");
4022 void SaveLevelSetup_SeriesInfo()
4025 char *level_subdir = leveldir_current->subdir;
4026 char *level_nr_str = int2str(level_nr, 0);
4027 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4030 /* ----------------------------------------------------------------------- */
4031 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4032 /* ----------------------------------------------------------------------- */
4034 InitLevelSetupDirectory(level_subdir);
4036 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4038 if (!(file = fopen(filename, MODE_WRITE)))
4040 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4045 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4046 getCookie("LEVELSETUP")));
4047 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4049 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4050 handicap_level_str));
4054 SetFilePermissions(filename, PERMS_PRIVATE);