1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
22 #if !defined(PLATFORM_WIN32)
24 #include <sys/param.h>
34 #define NUM_LEVELCLASS_DESC 8
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
49 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
50 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
51 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
57 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
61 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
62 IS_LEVELCLASS_BD(n) ? 2 : \
63 IS_LEVELCLASS_EM(n) ? 3 : \
64 IS_LEVELCLASS_SP(n) ? 4 : \
65 IS_LEVELCLASS_DX(n) ? 5 : \
66 IS_LEVELCLASS_SB(n) ? 6 : \
67 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
68 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
72 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
73 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
74 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
78 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
79 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
80 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
83 #define TOKEN_VALUE_POSITION_SHORT 32
84 #define TOKEN_VALUE_POSITION_DEFAULT 40
85 #define TOKEN_COMMENT_POSITION_DEFAULT 60
87 #define MAX_COOKIE_LEN 256
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
94 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
102 /* ------------------------------------------------------------------------- */
104 /* ------------------------------------------------------------------------- */
106 static char *getLevelClassDescription(TreeInfo *ti)
108 int position = ti->sort_priority / 100;
110 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111 return levelclass_desc[position];
113 return "Unknown Level Class";
116 static char *getUserLevelDir(char *level_subdir)
118 static char *userlevel_dir = NULL;
119 char *data_dir = getUserGameDataDir();
120 char *userlevel_subdir = LEVELS_DIRECTORY;
122 checked_free(userlevel_dir);
124 if (level_subdir != NULL)
125 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
127 userlevel_dir = getPath2(data_dir, userlevel_subdir);
129 return userlevel_dir;
132 static char *getScoreDir(char *level_subdir)
134 static char *score_dir = NULL;
135 char *data_dir = getCommonDataDir();
136 char *score_subdir = SCORES_DIRECTORY;
138 checked_free(score_dir);
140 if (level_subdir != NULL)
141 score_dir = getPath3(data_dir, score_subdir, level_subdir);
143 score_dir = getPath2(data_dir, score_subdir);
148 static char *getLevelSetupDir(char *level_subdir)
150 static char *levelsetup_dir = NULL;
151 char *data_dir = getUserGameDataDir();
152 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
154 checked_free(levelsetup_dir);
156 if (level_subdir != NULL)
157 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
159 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
161 return levelsetup_dir;
164 static char *getCacheDir()
166 static char *cache_dir = NULL;
168 if (cache_dir == NULL)
169 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
176 static char *level_dir = NULL;
179 return options.level_directory;
181 checked_free(level_dir);
183 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184 options.level_directory), node->fullpath);
189 char *getCurrentLevelDir()
191 return getLevelDirFromTreeInfo(leveldir_current);
194 static char *getTapeDir(char *level_subdir)
196 static char *tape_dir = NULL;
197 char *data_dir = getUserGameDataDir();
198 char *tape_subdir = TAPES_DIRECTORY;
200 checked_free(tape_dir);
202 if (level_subdir != NULL)
203 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
205 tape_dir = getPath2(data_dir, tape_subdir);
210 static char *getSolutionTapeDir()
212 static char *tape_dir = NULL;
213 char *data_dir = getCurrentLevelDir();
214 char *tape_subdir = TAPES_DIRECTORY;
216 checked_free(tape_dir);
218 tape_dir = getPath2(data_dir, tape_subdir);
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
225 static char *graphics_dir = NULL;
227 if (graphics_subdir == NULL)
228 return options.graphics_directory;
230 checked_free(graphics_dir);
232 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
237 static char *getDefaultSoundsDir(char *sounds_subdir)
239 static char *sounds_dir = NULL;
241 if (sounds_subdir == NULL)
242 return options.sounds_directory;
244 checked_free(sounds_dir);
246 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
251 static char *getDefaultMusicDir(char *music_subdir)
253 static char *music_dir = NULL;
255 if (music_subdir == NULL)
256 return options.music_directory;
258 checked_free(music_dir);
260 music_dir = getPath2(options.music_directory, music_subdir);
265 static char *getDefaultArtworkSet(int type)
267 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
269 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
272 static char *getDefaultArtworkDir(int type)
274 return (type == TREE_TYPE_GRAPHICS_DIR ?
275 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276 type == TREE_TYPE_SOUNDS_DIR ?
277 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278 type == TREE_TYPE_MUSIC_DIR ?
279 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
282 static char *getUserGraphicsDir()
284 static char *usergraphics_dir = NULL;
286 if (usergraphics_dir == NULL)
287 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
289 return usergraphics_dir;
292 static char *getUserSoundsDir()
294 static char *usersounds_dir = NULL;
296 if (usersounds_dir == NULL)
297 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
299 return usersounds_dir;
302 static char *getUserMusicDir()
304 static char *usermusic_dir = NULL;
306 if (usermusic_dir == NULL)
307 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
309 return usermusic_dir;
312 static char *getSetupArtworkDir(TreeInfo *ti)
314 static char *artwork_dir = NULL;
316 checked_free(artwork_dir);
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 char *setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, **artwork_set_ptr;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
331 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
334 checked_free(*artwork_path_ptr);
336 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
341 normally result in using the artwork configured in the setup menu. But
342 if an artwork subdirectory exists (which might contain custom artwork
343 or an artwork configuration file), this level artwork must be treated
344 as relative to the default "classic" artwork, not to the artwork that
345 is currently configured in the setup menu. */
347 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
349 checked_free(*artwork_set_ptr);
353 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
354 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
358 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
359 *artwork_set_ptr = NULL;
365 return *artwork_set_ptr;
368 inline static char *getLevelArtworkSet(int type)
370 if (leveldir_current == NULL)
373 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
376 inline static char *getLevelArtworkDir(int type)
378 if (leveldir_current == NULL)
379 return UNDEFINED_FILENAME;
381 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
384 char *getTapeFilename(int nr)
386 static char *filename = NULL;
387 char basename[MAX_FILENAME_LEN];
389 checked_free(filename);
391 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
392 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
397 char *getSolutionTapeFilename(int nr)
399 static char *filename = NULL;
400 char basename[MAX_FILENAME_LEN];
402 checked_free(filename);
404 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
405 filename = getPath2(getSolutionTapeDir(), basename);
410 char *getScoreFilename(int nr)
412 static char *filename = NULL;
413 char basename[MAX_FILENAME_LEN];
415 checked_free(filename);
417 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
418 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
423 char *getSetupFilename()
425 static char *filename = NULL;
427 checked_free(filename);
429 filename = getPath2(getSetupDir(), SETUP_FILENAME);
434 char *getEditorSetupFilename()
436 static char *filename = NULL;
438 checked_free(filename);
439 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
441 if (fileExists(filename))
444 checked_free(filename);
445 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
450 char *getHelpAnimFilename()
452 static char *filename = NULL;
454 checked_free(filename);
456 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
461 char *getHelpTextFilename()
463 static char *filename = NULL;
465 checked_free(filename);
467 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
472 char *getLevelSetInfoFilename()
474 static char *filename = NULL;
489 for (i = 0; basenames[i] != NULL; i++)
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), basenames[i]);
494 if (fileExists(filename))
501 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
503 static char *filename = NULL;
506 sprintf(basename, "%s_%d.txt",
507 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
509 checked_free(filename);
510 filename = getPath2(getCurrentLevelDir(), basename);
512 if (fileExists(filename))
518 static char *getCorrectedArtworkBasename(char *basename)
520 char *basename_corrected = basename;
522 #if defined(PLATFORM_MSDOS)
523 if (program.filename_prefix != NULL)
525 int prefix_len = strlen(program.filename_prefix);
527 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
528 basename_corrected = &basename[prefix_len];
530 /* if corrected filename is still longer than standard MS-DOS filename
531 size (8 characters + 1 dot + 3 characters file extension), shorten
532 filename by writing file extension after 8th basename character */
533 if (strlen(basename_corrected) > 8 + 1 + 3)
535 static char *msdos_filename = NULL;
537 checked_free(msdos_filename);
539 msdos_filename = getStringCopy(basename_corrected);
540 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
542 basename_corrected = msdos_filename;
547 return basename_corrected;
550 char *getCustomImageFilename(char *basename)
552 static char *filename = NULL;
553 boolean skip_setup_artwork = FALSE;
555 checked_free(filename);
557 basename = getCorrectedArtworkBasename(basename);
559 if (!setup.override_level_graphics)
561 /* 1st try: look for special artwork in current level series directory */
562 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
563 if (fileExists(filename))
568 /* check if there is special artwork configured in level series config */
569 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
571 /* 2nd try: look for special artwork configured in level series config */
572 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
573 if (fileExists(filename))
578 /* take missing artwork configured in level set config from default */
579 skip_setup_artwork = TRUE;
583 if (!skip_setup_artwork)
585 /* 3rd try: look for special artwork in configured artwork directory */
586 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
587 if (fileExists(filename))
593 /* 4th try: look for default artwork in new default artwork directory */
594 filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
595 if (fileExists(filename))
600 /* 5th try: look for default artwork in old default artwork directory */
601 filename = getPath2(options.graphics_directory, basename);
602 if (fileExists(filename))
605 #if CREATE_SPECIAL_EDITION
608 /* 6th try: look for fallback artwork in old default artwork directory */
609 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
610 if (fileExists(filename))
614 return NULL; /* cannot find specified artwork file anywhere */
617 char *getCustomSoundFilename(char *basename)
619 static char *filename = NULL;
620 boolean skip_setup_artwork = FALSE;
622 checked_free(filename);
624 basename = getCorrectedArtworkBasename(basename);
626 if (!setup.override_level_sounds)
628 /* 1st try: look for special artwork in current level series directory */
629 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
630 if (fileExists(filename))
635 /* check if there is special artwork configured in level series config */
636 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
638 /* 2nd try: look for special artwork configured in level series config */
639 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
640 if (fileExists(filename))
645 /* take missing artwork configured in level set config from default */
646 skip_setup_artwork = TRUE;
650 if (!skip_setup_artwork)
652 /* 3rd try: look for special artwork in configured artwork directory */
653 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
654 if (fileExists(filename))
660 /* 4th try: look for default artwork in new default artwork directory */
661 filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
662 if (fileExists(filename))
667 /* 5th try: look for default artwork in old default artwork directory */
668 filename = getPath2(options.sounds_directory, basename);
669 if (fileExists(filename))
672 #if CREATE_SPECIAL_EDITION
675 /* 6th try: look for fallback artwork in old default artwork directory */
676 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
677 if (fileExists(filename))
681 return NULL; /* cannot find specified artwork file anywhere */
684 char *getCustomMusicFilename(char *basename)
686 static char *filename = NULL;
687 boolean skip_setup_artwork = FALSE;
689 checked_free(filename);
691 basename = getCorrectedArtworkBasename(basename);
693 if (!setup.override_level_music)
695 /* 1st try: look for special artwork in current level series directory */
696 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
697 if (fileExists(filename))
702 /* check if there is special artwork configured in level series config */
703 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
705 /* 2nd try: look for special artwork configured in level series config */
706 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
707 if (fileExists(filename))
712 /* take missing artwork configured in level set config from default */
713 skip_setup_artwork = TRUE;
717 if (!skip_setup_artwork)
719 /* 3rd try: look for special artwork in configured artwork directory */
720 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
721 if (fileExists(filename))
727 /* 4th try: look for default artwork in new default artwork directory */
728 filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
729 if (fileExists(filename))
734 /* 5th try: look for default artwork in old default artwork directory */
735 filename = getPath2(options.music_directory, basename);
736 if (fileExists(filename))
739 #if CREATE_SPECIAL_EDITION
742 /* 6th try: look for fallback artwork in old default artwork directory */
743 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
744 if (fileExists(filename))
748 return NULL; /* cannot find specified artwork file anywhere */
751 char *getCustomArtworkFilename(char *basename, int type)
753 if (type == ARTWORK_TYPE_GRAPHICS)
754 return getCustomImageFilename(basename);
755 else if (type == ARTWORK_TYPE_SOUNDS)
756 return getCustomSoundFilename(basename);
757 else if (type == ARTWORK_TYPE_MUSIC)
758 return getCustomMusicFilename(basename);
760 return UNDEFINED_FILENAME;
763 char *getCustomArtworkConfigFilename(int type)
765 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
768 char *getCustomArtworkLevelConfigFilename(int type)
770 static char *filename = NULL;
772 checked_free(filename);
774 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
779 char *getCustomMusicDirectory(void)
781 static char *directory = NULL;
782 boolean skip_setup_artwork = FALSE;
784 checked_free(directory);
786 if (!setup.override_level_music)
788 /* 1st try: look for special artwork in current level series directory */
789 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
790 if (fileExists(directory))
795 /* check if there is special artwork configured in level series config */
796 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
798 /* 2nd try: look for special artwork configured in level series config */
799 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
800 if (fileExists(directory))
805 /* take missing artwork configured in level set config from default */
806 skip_setup_artwork = TRUE;
810 if (!skip_setup_artwork)
812 /* 3rd try: look for special artwork in configured artwork directory */
813 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
814 if (fileExists(directory))
820 /* 4th try: look for default artwork in new default artwork directory */
821 directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
822 if (fileExists(directory))
827 /* 5th try: look for default artwork in old default artwork directory */
828 directory = getStringCopy(options.music_directory);
829 if (fileExists(directory))
832 return NULL; /* cannot find specified artwork file anywhere */
835 void InitTapeDirectory(char *level_subdir)
837 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
838 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
839 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
842 void InitScoreDirectory(char *level_subdir)
844 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
845 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
846 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
849 static void SaveUserLevelInfo();
851 void InitUserLevelDirectory(char *level_subdir)
853 if (!fileExists(getUserLevelDir(level_subdir)))
855 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
856 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
857 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
863 void InitLevelSetupDirectory(char *level_subdir)
865 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
866 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
867 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
870 void InitCacheDirectory()
872 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
873 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
877 /* ------------------------------------------------------------------------- */
878 /* some functions to handle lists of level and artwork directories */
879 /* ------------------------------------------------------------------------- */
881 TreeInfo *newTreeInfo()
883 return checked_calloc(sizeof(TreeInfo));
886 TreeInfo *newTreeInfo_setDefaults(int type)
888 TreeInfo *ti = newTreeInfo();
890 setTreeInfoToDefaults(ti, type);
895 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
897 node_new->next = *node_first;
898 *node_first = node_new;
901 int numTreeInfo(TreeInfo *node)
914 boolean validLevelSeries(TreeInfo *node)
916 return (node != NULL && !node->node_group && !node->parent_link);
919 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
924 if (node->node_group) /* enter level group (step down into tree) */
925 return getFirstValidTreeInfoEntry(node->node_group);
926 else if (node->parent_link) /* skip start entry of level group */
928 if (node->next) /* get first real level series entry */
929 return getFirstValidTreeInfoEntry(node->next);
930 else /* leave empty level group and go on */
931 return getFirstValidTreeInfoEntry(node->node_parent->next);
933 else /* this seems to be a regular level series */
937 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
942 if (node->node_parent == NULL) /* top level group */
943 return *node->node_top;
944 else /* sub level group */
945 return node->node_parent->node_group;
948 int numTreeInfoInGroup(TreeInfo *node)
950 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
953 int posTreeInfo(TreeInfo *node)
955 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
960 if (node_cmp == node)
964 node_cmp = node_cmp->next;
970 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
972 TreeInfo *node_default = node;
987 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
989 if (identifier == NULL)
994 if (node->node_group)
996 TreeInfo *node_group;
998 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1003 else if (!node->parent_link)
1005 if (strEqual(identifier, node->identifier))
1015 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1016 TreeInfo *node, boolean skip_sets_without_levels)
1023 if (!node->parent_link && !node->level_group &&
1024 skip_sets_without_levels && node->levels == 0)
1025 return cloneTreeNode(node_top, node_parent, node->next,
1026 skip_sets_without_levels);
1029 node_new = getTreeInfoCopy(node); /* copy complete node */
1031 node_new = newTreeInfo();
1033 *node_new = *node; /* copy complete node */
1036 node_new->node_top = node_top; /* correct top node link */
1037 node_new->node_parent = node_parent; /* correct parent node link */
1039 if (node->level_group)
1040 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1041 skip_sets_without_levels);
1043 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1044 skip_sets_without_levels);
1049 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1051 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1053 *ti_new = ti_cloned;
1056 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1058 boolean settings_changed = FALSE;
1062 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1063 !strEqual(node->graphics_set, node->graphics_set_ecs))
1065 setString(&node->graphics_set, node->graphics_set_ecs);
1066 settings_changed = TRUE;
1068 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1069 !strEqual(node->graphics_set, node->graphics_set_aga))
1071 setString(&node->graphics_set, node->graphics_set_aga);
1072 settings_changed = TRUE;
1075 if (node->node_group != NULL)
1076 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1081 return settings_changed;
1084 void dumpTreeInfo(TreeInfo *node, int depth)
1088 printf("Dumping TreeInfo:\n");
1092 for (i = 0; i < (depth + 1) * 3; i++)
1095 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1096 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1098 if (node->node_group != NULL)
1099 dumpTreeInfo(node->node_group, depth + 1);
1105 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1106 int (*compare_function)(const void *,
1109 int num_nodes = numTreeInfo(*node_first);
1110 TreeInfo **sort_array;
1111 TreeInfo *node = *node_first;
1117 /* allocate array for sorting structure pointers */
1118 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1120 /* writing structure pointers to sorting array */
1121 while (i < num_nodes && node) /* double boundary check... */
1123 sort_array[i] = node;
1129 /* sorting the structure pointers in the sorting array */
1130 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1133 /* update the linkage of list elements with the sorted node array */
1134 for (i = 0; i < num_nodes - 1; i++)
1135 sort_array[i]->next = sort_array[i + 1];
1136 sort_array[num_nodes - 1]->next = NULL;
1138 /* update the linkage of the main list anchor pointer */
1139 *node_first = sort_array[0];
1143 /* now recursively sort the level group structures */
1147 if (node->node_group != NULL)
1148 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1154 void sortTreeInfo(TreeInfo **node_first)
1156 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1160 /* ========================================================================= */
1161 /* some stuff from "files.c" */
1162 /* ========================================================================= */
1164 #if defined(PLATFORM_WIN32)
1166 #define S_IRGRP S_IRUSR
1169 #define S_IROTH S_IRUSR
1172 #define S_IWGRP S_IWUSR
1175 #define S_IWOTH S_IWUSR
1178 #define S_IXGRP S_IXUSR
1181 #define S_IXOTH S_IXUSR
1184 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1189 #endif /* PLATFORM_WIN32 */
1191 /* file permissions for newly written files */
1192 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1193 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1194 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1196 #define MODE_W_PRIVATE (S_IWUSR)
1197 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1198 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1200 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1201 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1203 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1204 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1208 static char *dir = NULL;
1210 #if defined(PLATFORM_WIN32)
1213 dir = checked_malloc(MAX_PATH + 1);
1215 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1218 #elif defined(PLATFORM_UNIX)
1221 if ((dir = getenv("HOME")) == NULL)
1225 if ((pwd = getpwuid(getuid())) != NULL)
1226 dir = getStringCopy(pwd->pw_dir);
1238 char *getCommonDataDir(void)
1240 static char *common_data_dir = NULL;
1242 #if defined(PLATFORM_WIN32)
1243 if (common_data_dir == NULL)
1245 char *dir = checked_malloc(MAX_PATH + 1);
1247 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1248 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1249 common_data_dir = getPath2(dir, program.userdata_subdir);
1251 common_data_dir = options.rw_base_directory;
1254 if (common_data_dir == NULL)
1255 common_data_dir = options.rw_base_directory;
1258 return common_data_dir;
1261 char *getPersonalDataDir(void)
1263 static char *personal_data_dir = NULL;
1265 #if defined(PLATFORM_MACOSX)
1266 if (personal_data_dir == NULL)
1267 personal_data_dir = getPath2(getHomeDir(), "Documents");
1269 if (personal_data_dir == NULL)
1270 personal_data_dir = getHomeDir();
1273 return personal_data_dir;
1276 char *getUserGameDataDir(void)
1278 static char *user_game_data_dir = NULL;
1280 if (user_game_data_dir == NULL)
1281 user_game_data_dir = getPath2(getPersonalDataDir(),
1282 program.userdata_subdir);
1284 return user_game_data_dir;
1287 void updateUserGameDataDir()
1289 #if defined(PLATFORM_MACOSX)
1290 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1291 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1293 /* convert old Unix style game data directory to Mac OS X style, if needed */
1294 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1296 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1298 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1299 userdata_dir_old, userdata_dir_new);
1301 /* continue using Unix style data directory -- this should not happen */
1302 program.userdata_path = getPath2(getPersonalDataDir(),
1303 program.userdata_subdir_unix);
1307 free(userdata_dir_old);
1313 return getUserGameDataDir();
1316 static mode_t posix_umask(mode_t mask)
1318 #if defined(PLATFORM_UNIX)
1325 static int posix_mkdir(const char *pathname, mode_t mode)
1327 #if defined(PLATFORM_WIN32)
1328 return mkdir(pathname);
1330 return mkdir(pathname, mode);
1334 void createDirectory(char *dir, char *text, int permission_class)
1336 /* leave "other" permissions in umask untouched, but ensure group parts
1337 of USERDATA_DIR_MODE are not masked */
1338 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1339 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1340 mode_t normal_umask = posix_umask(0);
1341 mode_t group_umask = ~(dir_mode & S_IRWXG);
1342 posix_umask(normal_umask & group_umask);
1344 if (!fileExists(dir))
1345 if (posix_mkdir(dir, dir_mode) != 0)
1346 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1348 posix_umask(normal_umask); /* reset normal umask */
1351 void InitUserDataDirectory()
1353 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1356 void SetFilePermissions(char *filename, int permission_class)
1358 chmod(filename, (permission_class == PERMS_PRIVATE ?
1359 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1362 char *getCookie(char *file_type)
1364 static char cookie[MAX_COOKIE_LEN + 1];
1366 if (strlen(program.cookie_prefix) + 1 +
1367 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1368 return "[COOKIE ERROR]"; /* should never happen */
1370 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1371 program.cookie_prefix, file_type,
1372 program.version_major, program.version_minor);
1377 int getFileVersionFromCookieString(const char *cookie)
1379 const char *ptr_cookie1, *ptr_cookie2;
1380 const char *pattern1 = "_FILE_VERSION_";
1381 const char *pattern2 = "?.?";
1382 const int len_cookie = strlen(cookie);
1383 const int len_pattern1 = strlen(pattern1);
1384 const int len_pattern2 = strlen(pattern2);
1385 const int len_pattern = len_pattern1 + len_pattern2;
1386 int version_major, version_minor;
1388 if (len_cookie <= len_pattern)
1391 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1392 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1394 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1397 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1398 ptr_cookie2[1] != '.' ||
1399 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1402 version_major = ptr_cookie2[0] - '0';
1403 version_minor = ptr_cookie2[2] - '0';
1405 return VERSION_IDENT(version_major, version_minor, 0, 0);
1408 boolean checkCookieString(const char *cookie, const char *template)
1410 const char *pattern = "_FILE_VERSION_?.?";
1411 const int len_cookie = strlen(cookie);
1412 const int len_template = strlen(template);
1413 const int len_pattern = strlen(pattern);
1415 if (len_cookie != len_template)
1418 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1424 /* ------------------------------------------------------------------------- */
1425 /* setup file list and hash handling functions */
1426 /* ------------------------------------------------------------------------- */
1428 char *getFormattedSetupEntry(char *token, char *value)
1431 static char entry[MAX_LINE_LEN];
1433 /* if value is an empty string, just return token without value */
1437 /* start with the token and some spaces to format output line */
1438 sprintf(entry, "%s:", token);
1439 for (i = strlen(entry); i < token_value_position; i++)
1442 /* continue with the token's value */
1443 strcat(entry, value);
1448 SetupFileList *newSetupFileList(char *token, char *value)
1450 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1452 new->token = getStringCopy(token);
1453 new->value = getStringCopy(value);
1460 void freeSetupFileList(SetupFileList *list)
1465 checked_free(list->token);
1466 checked_free(list->value);
1469 freeSetupFileList(list->next);
1474 char *getListEntry(SetupFileList *list, char *token)
1479 if (strEqual(list->token, token))
1482 return getListEntry(list->next, token);
1485 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1490 if (strEqual(list->token, token))
1492 checked_free(list->value);
1494 list->value = getStringCopy(value);
1498 else if (list->next == NULL)
1499 return (list->next = newSetupFileList(token, value));
1501 return setListEntry(list->next, token, value);
1504 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1509 if (list->next == NULL)
1510 return (list->next = newSetupFileList(token, value));
1512 return addListEntry(list->next, token, value);
1516 static void printSetupFileList(SetupFileList *list)
1521 printf("token: '%s'\n", list->token);
1522 printf("value: '%s'\n", list->value);
1524 printSetupFileList(list->next);
1529 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1530 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1531 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1532 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1534 #define insert_hash_entry hashtable_insert
1535 #define search_hash_entry hashtable_search
1536 #define change_hash_entry hashtable_change
1537 #define remove_hash_entry hashtable_remove
1540 static unsigned int get_hash_from_key(void *key)
1545 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1546 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1547 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1548 it works better than many other constants, prime or not) has never been
1549 adequately explained.
1551 If you just want to have a good hash function, and cannot wait, djb2
1552 is one of the best string hash functions i know. It has excellent
1553 distribution and speed on many different sets of keys and table sizes.
1554 You are not likely to do better with one of the "well known" functions
1555 such as PJW, K&R, etc.
1557 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1560 char *str = (char *)key;
1561 unsigned int hash = 5381;
1564 while ((c = *str++))
1565 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1570 static int keys_are_equal(void *key1, void *key2)
1572 return (strEqual((char *)key1, (char *)key2));
1575 SetupFileHash *newSetupFileHash()
1577 SetupFileHash *new_hash =
1578 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1580 if (new_hash == NULL)
1581 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1586 void freeSetupFileHash(SetupFileHash *hash)
1591 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1594 char *getHashEntry(SetupFileHash *hash, char *token)
1599 return search_hash_entry(hash, token);
1602 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1609 value_copy = getStringCopy(value);
1611 /* change value; if it does not exist, insert it as new */
1612 if (!change_hash_entry(hash, token, value_copy))
1613 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1614 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1617 char *removeHashEntry(SetupFileHash *hash, char *token)
1622 return remove_hash_entry(hash, token);
1626 static void printSetupFileHash(SetupFileHash *hash)
1628 BEGIN_HASH_ITERATION(hash, itr)
1630 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1631 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1633 END_HASH_ITERATION(hash, itr)
1637 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1638 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1639 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1641 static boolean token_value_separator_found = FALSE;
1642 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1643 static boolean token_value_separator_warning = FALSE;
1645 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1646 static boolean token_already_exists_warning = FALSE;
1649 static boolean getTokenValueFromSetupLineExt(char *line,
1650 char **token_ptr, char **value_ptr,
1651 char *filename, char *line_raw,
1653 boolean separator_required)
1655 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1656 char *token, *value, *line_ptr;
1658 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1659 if (line_raw == NULL)
1661 strncpy(line_copy, line, MAX_LINE_LEN);
1662 line_copy[MAX_LINE_LEN] = '\0';
1665 strcpy(line_raw_copy, line_copy);
1666 line_raw = line_raw_copy;
1669 /* cut trailing comment from input line */
1670 for (line_ptr = line; *line_ptr; line_ptr++)
1672 if (*line_ptr == '#')
1679 /* cut trailing whitespaces from input line */
1680 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1681 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1684 /* ignore empty lines */
1688 /* cut leading whitespaces from token */
1689 for (token = line; *token; token++)
1690 if (*token != ' ' && *token != '\t')
1693 /* start with empty value as reliable default */
1696 token_value_separator_found = FALSE;
1698 /* find end of token to determine start of value */
1699 for (line_ptr = token; *line_ptr; line_ptr++)
1702 /* first look for an explicit token/value separator, like ':' or '=' */
1703 if (*line_ptr == ':' || *line_ptr == '=')
1705 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1708 *line_ptr = '\0'; /* terminate token string */
1709 value = line_ptr + 1; /* set beginning of value */
1711 token_value_separator_found = TRUE;
1717 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1718 /* fallback: if no token/value separator found, also allow whitespaces */
1719 if (!token_value_separator_found && !separator_required)
1721 for (line_ptr = token; *line_ptr; line_ptr++)
1723 if (*line_ptr == ' ' || *line_ptr == '\t')
1725 *line_ptr = '\0'; /* terminate token string */
1726 value = line_ptr + 1; /* set beginning of value */
1728 token_value_separator_found = TRUE;
1734 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1735 if (token_value_separator_found)
1737 if (!token_value_separator_warning)
1739 Error(ERR_INFO_LINE, "-");
1741 if (filename != NULL)
1743 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1744 Error(ERR_INFO, "- config file: '%s'", filename);
1748 Error(ERR_WARN, "missing token/value separator(s):");
1751 token_value_separator_warning = TRUE;
1754 if (filename != NULL)
1755 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1757 Error(ERR_INFO, "- line: '%s'", line_raw);
1763 /* cut trailing whitespaces from token */
1764 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1765 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1768 /* cut leading whitespaces from value */
1769 for (; *value; value++)
1770 if (*value != ' ' && *value != '\t')
1775 value = "true"; /* treat tokens without value as "true" */
1784 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1786 /* while the internal (old) interface does not require a token/value
1787 separator (for downwards compatibility with existing files which
1788 don't use them), it is mandatory for the external (new) interface */
1790 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1794 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1795 boolean top_recursion_level, boolean is_hash)
1797 static SetupFileHash *include_filename_hash = NULL;
1798 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1799 char *token, *value, *line_ptr;
1800 void *insert_ptr = NULL;
1801 boolean read_continued_line = FALSE;
1803 int line_nr = 0, token_count = 0, include_count = 0;
1805 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1806 token_value_separator_warning = FALSE;
1809 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1810 token_already_exists_warning = FALSE;
1813 if (!(file = fopen(filename, MODE_READ)))
1815 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1820 /* use "insert pointer" to store list end for constant insertion complexity */
1822 insert_ptr = setup_file_data;
1824 /* on top invocation, create hash to mark included files (to prevent loops) */
1825 if (top_recursion_level)
1826 include_filename_hash = newSetupFileHash();
1828 /* mark this file as already included (to prevent including it again) */
1829 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1833 /* read next line of input file */
1834 if (!fgets(line, MAX_LINE_LEN, file))
1837 /* check if line was completely read and is terminated by line break */
1838 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1841 /* cut trailing line break (this can be newline and/or carriage return) */
1842 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1843 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1846 /* copy raw input line for later use (mainly debugging output) */
1847 strcpy(line_raw, line);
1849 if (read_continued_line)
1852 /* !!! ??? WHY ??? !!! */
1853 /* cut leading whitespaces from input line */
1854 for (line_ptr = line; *line_ptr; line_ptr++)
1855 if (*line_ptr != ' ' && *line_ptr != '\t')
1859 /* append new line to existing line, if there is enough space */
1860 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1861 strcat(previous_line, line_ptr);
1863 strcpy(line, previous_line); /* copy storage buffer to line */
1865 read_continued_line = FALSE;
1868 /* if the last character is '\', continue at next line */
1869 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1871 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1872 strcpy(previous_line, line); /* copy line to storage buffer */
1874 read_continued_line = TRUE;
1879 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1880 line_raw, line_nr, FALSE))
1885 if (strEqual(token, "include"))
1887 if (getHashEntry(include_filename_hash, value) == NULL)
1889 char *basepath = getBasePath(filename);
1890 char *basename = getBaseName(value);
1891 char *filename_include = getPath2(basepath, basename);
1894 Error(ERR_INFO, "[including file '%s']", filename_include);
1897 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1901 free(filename_include);
1907 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1914 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1916 getHashEntry((SetupFileHash *)setup_file_data, token);
1918 if (old_value != NULL)
1920 if (!token_already_exists_warning)
1922 Error(ERR_INFO_LINE, "-");
1923 Error(ERR_WARN, "duplicate token(s) found in config file:");
1924 Error(ERR_INFO, "- config file: '%s'", filename);
1926 token_already_exists_warning = TRUE;
1929 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
1930 Error(ERR_INFO, " old value: '%s'", old_value);
1931 Error(ERR_INFO, " new value: '%s'", value);
1935 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1939 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1949 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1950 if (token_value_separator_warning)
1951 Error(ERR_INFO_LINE, "-");
1954 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1955 if (token_already_exists_warning)
1956 Error(ERR_INFO_LINE, "-");
1959 if (token_count == 0 && include_count == 0)
1960 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1962 if (top_recursion_level)
1963 freeSetupFileHash(include_filename_hash);
1970 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1971 boolean top_recursion_level, boolean is_hash)
1973 static SetupFileHash *include_filename_hash = NULL;
1974 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1975 char *token, *value, *line_ptr;
1976 void *insert_ptr = NULL;
1977 boolean read_continued_line = FALSE;
1980 int token_count = 0;
1982 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1983 token_value_separator_warning = FALSE;
1986 if (!(file = fopen(filename, MODE_READ)))
1988 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1993 /* use "insert pointer" to store list end for constant insertion complexity */
1995 insert_ptr = setup_file_data;
1997 /* on top invocation, create hash to mark included files (to prevent loops) */
1998 if (top_recursion_level)
1999 include_filename_hash = newSetupFileHash();
2001 /* mark this file as already included (to prevent including it again) */
2002 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2006 /* read next line of input file */
2007 if (!fgets(line, MAX_LINE_LEN, file))
2010 /* check if line was completely read and is terminated by line break */
2011 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2014 /* cut trailing line break (this can be newline and/or carriage return) */
2015 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2016 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2019 /* copy raw input line for later use (mainly debugging output) */
2020 strcpy(line_raw, line);
2022 if (read_continued_line)
2024 /* cut leading whitespaces from input line */
2025 for (line_ptr = line; *line_ptr; line_ptr++)
2026 if (*line_ptr != ' ' && *line_ptr != '\t')
2029 /* append new line to existing line, if there is enough space */
2030 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2031 strcat(previous_line, line_ptr);
2033 strcpy(line, previous_line); /* copy storage buffer to line */
2035 read_continued_line = FALSE;
2038 /* if the last character is '\', continue at next line */
2039 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2041 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2042 strcpy(previous_line, line); /* copy line to storage buffer */
2044 read_continued_line = TRUE;
2049 /* cut trailing comment from input line */
2050 for (line_ptr = line; *line_ptr; line_ptr++)
2052 if (*line_ptr == '#')
2059 /* cut trailing whitespaces from input line */
2060 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2061 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2064 /* ignore empty lines */
2068 /* cut leading whitespaces from token */
2069 for (token = line; *token; token++)
2070 if (*token != ' ' && *token != '\t')
2073 /* start with empty value as reliable default */
2076 token_value_separator_found = FALSE;
2078 /* find end of token to determine start of value */
2079 for (line_ptr = token; *line_ptr; line_ptr++)
2082 /* first look for an explicit token/value separator, like ':' or '=' */
2083 if (*line_ptr == ':' || *line_ptr == '=')
2085 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2088 *line_ptr = '\0'; /* terminate token string */
2089 value = line_ptr + 1; /* set beginning of value */
2091 token_value_separator_found = TRUE;
2097 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2098 /* fallback: if no token/value separator found, also allow whitespaces */
2099 if (!token_value_separator_found)
2101 for (line_ptr = token; *line_ptr; line_ptr++)
2103 if (*line_ptr == ' ' || *line_ptr == '\t')
2105 *line_ptr = '\0'; /* terminate token string */
2106 value = line_ptr + 1; /* set beginning of value */
2108 token_value_separator_found = TRUE;
2114 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2115 if (token_value_separator_found)
2117 if (!token_value_separator_warning)
2119 Error(ERR_INFO_LINE, "-");
2120 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2121 Error(ERR_INFO, "- config file: '%s'", filename);
2123 token_value_separator_warning = TRUE;
2126 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2132 /* cut trailing whitespaces from token */
2133 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2134 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2137 /* cut leading whitespaces from value */
2138 for (; *value; value++)
2139 if (*value != ' ' && *value != '\t')
2144 value = "true"; /* treat tokens without value as "true" */
2149 if (strEqual(token, "include"))
2151 if (getHashEntry(include_filename_hash, value) == NULL)
2153 char *basepath = getBasePath(filename);
2154 char *basename = getBaseName(value);
2155 char *filename_include = getPath2(basepath, basename);
2158 Error(ERR_INFO, "[including file '%s']", filename_include);
2161 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2165 free(filename_include);
2169 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2175 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2177 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2186 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2187 if (token_value_separator_warning)
2188 Error(ERR_INFO_LINE, "-");
2191 if (token_count == 0)
2192 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2194 if (top_recursion_level)
2195 freeSetupFileHash(include_filename_hash);
2201 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2205 if (!(file = fopen(filename, MODE_WRITE)))
2207 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2212 BEGIN_HASH_ITERATION(hash, itr)
2214 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2215 HASH_ITERATION_VALUE(itr)));
2217 END_HASH_ITERATION(hash, itr)
2222 SetupFileList *loadSetupFileList(char *filename)
2224 SetupFileList *setup_file_list = newSetupFileList("", "");
2225 SetupFileList *first_valid_list_entry;
2227 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2229 freeSetupFileList(setup_file_list);
2234 first_valid_list_entry = setup_file_list->next;
2236 /* free empty list header */
2237 setup_file_list->next = NULL;
2238 freeSetupFileList(setup_file_list);
2240 return first_valid_list_entry;
2243 SetupFileHash *loadSetupFileHash(char *filename)
2245 SetupFileHash *setup_file_hash = newSetupFileHash();
2247 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2249 freeSetupFileHash(setup_file_hash);
2254 return setup_file_hash;
2257 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2258 char *filename, char *identifier)
2260 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2263 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2264 else if (!checkCookieString(value, identifier))
2265 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2269 /* ========================================================================= */
2270 /* setup file stuff */
2271 /* ========================================================================= */
2273 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2274 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2275 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2277 /* level directory info */
2278 #define LEVELINFO_TOKEN_IDENTIFIER 0
2279 #define LEVELINFO_TOKEN_NAME 1
2280 #define LEVELINFO_TOKEN_NAME_SORTING 2
2281 #define LEVELINFO_TOKEN_AUTHOR 3
2282 #define LEVELINFO_TOKEN_YEAR 4
2283 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2284 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2285 #define LEVELINFO_TOKEN_TESTED_BY 7
2286 #define LEVELINFO_TOKEN_LEVELS 8
2287 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2288 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2289 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2290 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2291 #define LEVELINFO_TOKEN_READONLY 13
2292 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2293 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2294 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2295 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2296 #define LEVELINFO_TOKEN_MUSIC_SET 18
2297 #define LEVELINFO_TOKEN_FILENAME 19
2298 #define LEVELINFO_TOKEN_FILETYPE 20
2299 #define LEVELINFO_TOKEN_HANDICAP 21
2300 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2302 #define NUM_LEVELINFO_TOKENS 23
2304 static LevelDirTree ldi;
2306 static struct TokenInfo levelinfo_tokens[] =
2308 /* level directory info */
2309 { TYPE_STRING, &ldi.identifier, "identifier" },
2310 { TYPE_STRING, &ldi.name, "name" },
2311 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2312 { TYPE_STRING, &ldi.author, "author" },
2313 { TYPE_STRING, &ldi.year, "year" },
2314 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2315 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2316 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2317 { TYPE_INTEGER, &ldi.levels, "levels" },
2318 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2319 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2320 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2321 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2322 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2323 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2324 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2325 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2326 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2327 { TYPE_STRING, &ldi.music_set, "music_set" },
2328 { TYPE_STRING, &ldi.level_filename, "filename" },
2329 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2330 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2331 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2334 static struct TokenInfo artworkinfo_tokens[] =
2336 /* artwork directory info */
2337 { TYPE_STRING, &ldi.identifier, "identifier" },
2338 { TYPE_STRING, &ldi.subdir, "subdir" },
2339 { TYPE_STRING, &ldi.name, "name" },
2340 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2341 { TYPE_STRING, &ldi.author, "author" },
2342 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2343 { TYPE_STRING, &ldi.basepath, "basepath" },
2344 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2345 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2346 { TYPE_INTEGER, &ldi.color, "color" },
2347 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2352 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2356 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2357 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2358 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2359 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2362 ti->node_parent = NULL;
2363 ti->node_group = NULL;
2370 ti->fullpath = NULL;
2371 ti->basepath = NULL;
2372 ti->identifier = NULL;
2373 ti->name = getStringCopy(ANONYMOUS_NAME);
2374 ti->name_sorting = NULL;
2375 ti->author = getStringCopy(ANONYMOUS_NAME);
2378 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2379 ti->latest_engine = FALSE; /* default: get from level */
2380 ti->parent_link = FALSE;
2381 ti->in_user_dir = FALSE;
2382 ti->user_defined = FALSE;
2384 ti->class_desc = NULL;
2386 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2388 if (ti->type == TREE_TYPE_LEVEL_DIR)
2390 ti->imported_from = NULL;
2391 ti->imported_by = NULL;
2392 ti->tested_by = NULL;
2394 ti->graphics_set_ecs = NULL;
2395 ti->graphics_set_aga = NULL;
2396 ti->graphics_set = NULL;
2397 ti->sounds_set = NULL;
2398 ti->music_set = NULL;
2399 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2400 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2401 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2403 ti->level_filename = NULL;
2404 ti->level_filetype = NULL;
2407 ti->first_level = 0;
2409 ti->level_group = FALSE;
2410 ti->handicap_level = 0;
2411 ti->readonly = TRUE;
2412 ti->handicap = TRUE;
2413 ti->skip_levels = FALSE;
2417 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2421 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2423 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2428 /* copy all values from the parent structure */
2430 ti->type = parent->type;
2432 ti->node_top = parent->node_top;
2433 ti->node_parent = parent;
2434 ti->node_group = NULL;
2441 ti->fullpath = NULL;
2442 ti->basepath = NULL;
2443 ti->identifier = NULL;
2444 ti->name = getStringCopy(ANONYMOUS_NAME);
2445 ti->name_sorting = NULL;
2446 ti->author = getStringCopy(parent->author);
2447 ti->year = getStringCopy(parent->year);
2449 ti->sort_priority = parent->sort_priority;
2450 ti->latest_engine = parent->latest_engine;
2451 ti->parent_link = FALSE;
2452 ti->in_user_dir = parent->in_user_dir;
2453 ti->user_defined = parent->user_defined;
2454 ti->color = parent->color;
2455 ti->class_desc = getStringCopy(parent->class_desc);
2457 ti->infotext = getStringCopy(parent->infotext);
2459 if (ti->type == TREE_TYPE_LEVEL_DIR)
2461 ti->imported_from = getStringCopy(parent->imported_from);
2462 ti->imported_by = getStringCopy(parent->imported_by);
2463 ti->tested_by = getStringCopy(parent->tested_by);
2465 ti->graphics_set_ecs = NULL;
2466 ti->graphics_set_aga = NULL;
2467 ti->graphics_set = NULL;
2468 ti->sounds_set = NULL;
2469 ti->music_set = NULL;
2470 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2471 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2472 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2474 ti->level_filename = NULL;
2475 ti->level_filetype = NULL;
2478 ti->first_level = 0;
2480 ti->level_group = FALSE;
2481 ti->handicap_level = 0;
2482 ti->readonly = TRUE;
2483 ti->handicap = TRUE;
2484 ti->skip_levels = FALSE;
2488 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2490 TreeInfo *ti_copy = newTreeInfo();
2492 /* copy all values from the original structure */
2494 ti_copy->type = ti->type;
2496 ti_copy->node_top = ti->node_top;
2497 ti_copy->node_parent = ti->node_parent;
2498 ti_copy->node_group = ti->node_group;
2499 ti_copy->next = ti->next;
2501 ti_copy->cl_first = ti->cl_first;
2502 ti_copy->cl_cursor = ti->cl_cursor;
2504 ti_copy->subdir = getStringCopy(ti->subdir);
2505 ti_copy->fullpath = getStringCopy(ti->fullpath);
2506 ti_copy->basepath = getStringCopy(ti->basepath);
2507 ti_copy->identifier = getStringCopy(ti->identifier);
2508 ti_copy->name = getStringCopy(ti->name);
2509 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2510 ti_copy->author = getStringCopy(ti->author);
2511 ti_copy->year = getStringCopy(ti->year);
2512 ti_copy->imported_from = getStringCopy(ti->imported_from);
2513 ti_copy->imported_by = getStringCopy(ti->imported_by);
2514 ti_copy->tested_by = getStringCopy(ti->tested_by);
2516 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2517 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2518 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2519 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2520 ti_copy->music_set = getStringCopy(ti->music_set);
2521 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2522 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2523 ti_copy->music_path = getStringCopy(ti->music_path);
2525 ti_copy->level_filename = getStringCopy(ti->level_filename);
2526 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2528 ti_copy->levels = ti->levels;
2529 ti_copy->first_level = ti->first_level;
2530 ti_copy->last_level = ti->last_level;
2531 ti_copy->sort_priority = ti->sort_priority;
2533 ti_copy->latest_engine = ti->latest_engine;
2535 ti_copy->level_group = ti->level_group;
2536 ti_copy->parent_link = ti->parent_link;
2537 ti_copy->in_user_dir = ti->in_user_dir;
2538 ti_copy->user_defined = ti->user_defined;
2539 ti_copy->readonly = ti->readonly;
2540 ti_copy->handicap = ti->handicap;
2541 ti_copy->skip_levels = ti->skip_levels;
2543 ti_copy->color = ti->color;
2544 ti_copy->class_desc = getStringCopy(ti->class_desc);
2545 ti_copy->handicap_level = ti->handicap_level;
2547 ti_copy->infotext = getStringCopy(ti->infotext);
2552 static void freeTreeInfo(TreeInfo *ti)
2557 checked_free(ti->subdir);
2558 checked_free(ti->fullpath);
2559 checked_free(ti->basepath);
2560 checked_free(ti->identifier);
2562 checked_free(ti->name);
2563 checked_free(ti->name_sorting);
2564 checked_free(ti->author);
2565 checked_free(ti->year);
2567 checked_free(ti->class_desc);
2569 checked_free(ti->infotext);
2571 if (ti->type == TREE_TYPE_LEVEL_DIR)
2573 checked_free(ti->imported_from);
2574 checked_free(ti->imported_by);
2575 checked_free(ti->tested_by);
2577 checked_free(ti->graphics_set_ecs);
2578 checked_free(ti->graphics_set_aga);
2579 checked_free(ti->graphics_set);
2580 checked_free(ti->sounds_set);
2581 checked_free(ti->music_set);
2583 checked_free(ti->graphics_path);
2584 checked_free(ti->sounds_path);
2585 checked_free(ti->music_path);
2587 checked_free(ti->level_filename);
2588 checked_free(ti->level_filetype);
2594 void setSetupInfo(struct TokenInfo *token_info,
2595 int token_nr, char *token_value)
2597 int token_type = token_info[token_nr].type;
2598 void *setup_value = token_info[token_nr].value;
2600 if (token_value == NULL)
2603 /* set setup field to corresponding token value */
2608 *(boolean *)setup_value = get_boolean_from_string(token_value);
2612 *(Key *)setup_value = getKeyFromKeyName(token_value);
2616 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2620 *(int *)setup_value = get_integer_from_string(token_value);
2624 checked_free(*(char **)setup_value);
2625 *(char **)setup_value = getStringCopy(token_value);
2633 static int compareTreeInfoEntries(const void *object1, const void *object2)
2635 const TreeInfo *entry1 = *((TreeInfo **)object1);
2636 const TreeInfo *entry2 = *((TreeInfo **)object2);
2637 int class_sorting1, class_sorting2;
2640 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2642 class_sorting1 = LEVELSORTING(entry1);
2643 class_sorting2 = LEVELSORTING(entry2);
2647 class_sorting1 = ARTWORKSORTING(entry1);
2648 class_sorting2 = ARTWORKSORTING(entry2);
2651 if (entry1->parent_link || entry2->parent_link)
2652 compare_result = (entry1->parent_link ? -1 : +1);
2653 else if (entry1->sort_priority == entry2->sort_priority)
2655 char *name1 = getStringToLower(entry1->name_sorting);
2656 char *name2 = getStringToLower(entry2->name_sorting);
2658 compare_result = strcmp(name1, name2);
2663 else if (class_sorting1 == class_sorting2)
2664 compare_result = entry1->sort_priority - entry2->sort_priority;
2666 compare_result = class_sorting1 - class_sorting2;
2668 return compare_result;
2671 static void createParentTreeInfoNode(TreeInfo *node_parent)
2675 if (node_parent == NULL)
2678 ti_new = newTreeInfo();
2679 setTreeInfoToDefaults(ti_new, node_parent->type);
2681 ti_new->node_parent = node_parent;
2682 ti_new->parent_link = TRUE;
2684 setString(&ti_new->identifier, node_parent->identifier);
2685 setString(&ti_new->name, ".. (parent directory)");
2686 setString(&ti_new->name_sorting, ti_new->name);
2688 setString(&ti_new->subdir, "..");
2689 setString(&ti_new->fullpath, node_parent->fullpath);
2691 ti_new->sort_priority = node_parent->sort_priority;
2692 ti_new->latest_engine = node_parent->latest_engine;
2694 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2696 pushTreeInfo(&node_parent->node_group, ti_new);
2700 /* -------------------------------------------------------------------------- */
2701 /* functions for handling level and custom artwork info cache */
2702 /* -------------------------------------------------------------------------- */
2704 static void LoadArtworkInfoCache()
2706 InitCacheDirectory();
2708 if (artworkinfo_cache_old == NULL)
2710 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2712 /* try to load artwork info hash from already existing cache file */
2713 artworkinfo_cache_old = loadSetupFileHash(filename);
2715 /* if no artwork info cache file was found, start with empty hash */
2716 if (artworkinfo_cache_old == NULL)
2717 artworkinfo_cache_old = newSetupFileHash();
2722 if (artworkinfo_cache_new == NULL)
2723 artworkinfo_cache_new = newSetupFileHash();
2726 static void SaveArtworkInfoCache()
2728 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2730 InitCacheDirectory();
2732 saveSetupFileHash(artworkinfo_cache_new, filename);
2737 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2739 static char *prefix = NULL;
2741 checked_free(prefix);
2743 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2748 /* (identical to above function, but separate string buffer needed -- nasty) */
2749 static char *getCacheToken(char *prefix, char *suffix)
2751 static char *token = NULL;
2753 checked_free(token);
2755 token = getStringCat2WithSeparator(prefix, suffix, ".");
2760 static char *getFileTimestamp(char *filename)
2762 struct stat file_status;
2764 if (stat(filename, &file_status) != 0) /* cannot stat file */
2765 return getStringCopy(i_to_a(0));
2767 return getStringCopy(i_to_a(file_status.st_mtime));
2770 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2772 struct stat file_status;
2774 if (timestamp_string == NULL)
2777 if (stat(filename, &file_status) != 0) /* cannot stat file */
2780 return (file_status.st_mtime != atoi(timestamp_string));
2783 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2785 char *identifier = level_node->subdir;
2786 char *type_string = ARTWORK_DIRECTORY(type);
2787 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2788 char *token_main = getCacheToken(token_prefix, "CACHED");
2789 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2790 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2791 TreeInfo *artwork_info = NULL;
2793 if (!use_artworkinfo_cache)
2800 artwork_info = newTreeInfo();
2801 setTreeInfoToDefaults(artwork_info, type);
2803 /* set all structure fields according to the token/value pairs */
2804 ldi = *artwork_info;
2805 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2807 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2808 char *value = getHashEntry(artworkinfo_cache_old, token);
2810 setSetupInfo(artworkinfo_tokens, i, value);
2812 /* check if cache entry for this item is invalid or incomplete */
2816 Error(ERR_WARN, "cache entry '%s' invalid", token);
2823 *artwork_info = ldi;
2828 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2829 LEVELINFO_FILENAME);
2830 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2831 ARTWORKINFO_FILENAME(type));
2833 /* check if corresponding "levelinfo.conf" file has changed */
2834 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2835 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2837 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2840 /* check if corresponding "<artworkinfo>.conf" file has changed */
2841 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2842 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2844 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2849 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2852 checked_free(filename_levelinfo);
2853 checked_free(filename_artworkinfo);
2856 if (!cached && artwork_info != NULL)
2858 freeTreeInfo(artwork_info);
2863 return artwork_info;
2866 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2867 LevelDirTree *level_node, int type)
2869 char *identifier = level_node->subdir;
2870 char *type_string = ARTWORK_DIRECTORY(type);
2871 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2872 char *token_main = getCacheToken(token_prefix, "CACHED");
2873 boolean set_cache_timestamps = TRUE;
2876 setHashEntry(artworkinfo_cache_new, token_main, "true");
2878 if (set_cache_timestamps)
2880 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2881 LEVELINFO_FILENAME);
2882 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2883 ARTWORKINFO_FILENAME(type));
2884 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2885 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2887 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2888 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2890 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2891 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2893 checked_free(filename_levelinfo);
2894 checked_free(filename_artworkinfo);
2895 checked_free(timestamp_levelinfo);
2896 checked_free(timestamp_artworkinfo);
2899 ldi = *artwork_info;
2900 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2902 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2903 char *value = getSetupValue(artworkinfo_tokens[i].type,
2904 artworkinfo_tokens[i].value);
2906 setHashEntry(artworkinfo_cache_new, token, value);
2911 /* -------------------------------------------------------------------------- */
2912 /* functions for loading level info and custom artwork info */
2913 /* -------------------------------------------------------------------------- */
2915 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2916 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2918 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2919 TreeInfo *node_parent,
2920 char *level_directory,
2921 char *directory_name)
2923 static unsigned long progress_delay = 0;
2924 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2925 char *directory_path = getPath2(level_directory, directory_name);
2926 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2927 SetupFileHash *setup_file_hash;
2928 LevelDirTree *leveldir_new = NULL;
2931 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2932 if (!options.debug && !fileExists(filename))
2934 free(directory_path);
2940 setup_file_hash = loadSetupFileHash(filename);
2942 if (setup_file_hash == NULL)
2944 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2946 free(directory_path);
2952 leveldir_new = newTreeInfo();
2955 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2957 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2959 leveldir_new->subdir = getStringCopy(directory_name);
2961 checkSetupFileHashIdentifier(setup_file_hash, filename,
2962 getCookie("LEVELINFO"));
2964 /* set all structure fields according to the token/value pairs */
2965 ldi = *leveldir_new;
2966 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2967 setSetupInfo(levelinfo_tokens, i,
2968 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2969 *leveldir_new = ldi;
2971 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2972 setString(&leveldir_new->name, leveldir_new->subdir);
2974 if (leveldir_new->identifier == NULL)
2975 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2977 if (leveldir_new->name_sorting == NULL)
2978 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2980 if (node_parent == NULL) /* top level group */
2982 leveldir_new->basepath = getStringCopy(level_directory);
2983 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2985 else /* sub level group */
2987 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2988 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2992 if (leveldir_new->levels < 1)
2993 leveldir_new->levels = 1;
2996 leveldir_new->last_level =
2997 leveldir_new->first_level + leveldir_new->levels - 1;
2999 leveldir_new->in_user_dir =
3000 (!strEqual(leveldir_new->basepath, options.level_directory));
3002 /* adjust some settings if user's private level directory was detected */
3003 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3004 leveldir_new->in_user_dir &&
3005 (strEqual(leveldir_new->subdir, getLoginName()) ||
3006 strEqual(leveldir_new->name, getLoginName()) ||
3007 strEqual(leveldir_new->author, getRealName())))
3009 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3010 leveldir_new->readonly = FALSE;
3013 leveldir_new->user_defined =
3014 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3016 leveldir_new->color = LEVELCOLOR(leveldir_new);
3018 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3020 leveldir_new->handicap_level = /* set handicap to default value */
3021 (leveldir_new->user_defined || !leveldir_new->handicap ?
3022 leveldir_new->last_level : leveldir_new->first_level);
3025 if (leveldir_new->level_group ||
3026 DelayReached(&progress_delay, progress_delay_value))
3027 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3029 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3033 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3035 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3037 /* skip level sets without levels (which are probably artwork base sets) */
3039 freeSetupFileHash(setup_file_hash);
3040 free(directory_path);
3048 pushTreeInfo(node_first, leveldir_new);
3050 freeSetupFileHash(setup_file_hash);
3052 if (leveldir_new->level_group)
3054 /* create node to link back to current level directory */
3055 createParentTreeInfoNode(leveldir_new);
3057 /* recursively step into sub-directory and look for more level series */
3058 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3059 leveldir_new, directory_path);
3062 free(directory_path);
3068 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3069 TreeInfo *node_parent,
3070 char *level_directory)
3073 struct dirent *dir_entry;
3074 boolean valid_entry_found = FALSE;
3076 if ((dir = opendir(level_directory)) == NULL)
3078 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3082 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3084 struct stat file_status;
3085 char *directory_name = dir_entry->d_name;
3086 char *directory_path = getPath2(level_directory, directory_name);
3088 /* skip entries for current and parent directory */
3089 if (strEqual(directory_name, ".") ||
3090 strEqual(directory_name, ".."))
3092 free(directory_path);
3096 /* find out if directory entry is itself a directory */
3097 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3098 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3100 free(directory_path);
3104 free(directory_path);
3106 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3107 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3108 strEqual(directory_name, MUSIC_DIRECTORY))
3111 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3118 /* special case: top level directory may directly contain "levelinfo.conf" */
3119 if (node_parent == NULL && !valid_entry_found)
3121 /* check if this directory directly contains a file "levelinfo.conf" */
3122 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3123 level_directory, ".");
3126 if (!valid_entry_found)
3127 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3131 boolean AdjustGraphicsForEMC()
3133 boolean settings_changed = FALSE;
3135 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3136 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3138 return settings_changed;
3141 void LoadLevelInfo()
3143 InitUserLevelDirectory(getLoginName());
3145 DrawInitText("Loading level series", 120, FC_GREEN);
3147 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3148 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3150 /* after loading all level set information, clone the level directory tree
3151 and remove all level sets without levels (these may still contain artwork
3152 to be offered in the setup menu as "custom artwork", and are therefore
3153 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3154 leveldir_first_all = leveldir_first;
3155 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3157 AdjustGraphicsForEMC();
3159 /* before sorting, the first entries will be from the user directory */
3160 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3162 if (leveldir_first == NULL)
3163 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3165 sortTreeInfo(&leveldir_first);
3168 dumpTreeInfo(leveldir_first, 0);
3172 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3173 TreeInfo *node_parent,
3174 char *base_directory,
3175 char *directory_name, int type)
3177 char *directory_path = getPath2(base_directory, directory_name);
3178 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3179 SetupFileHash *setup_file_hash = NULL;
3180 TreeInfo *artwork_new = NULL;
3183 if (fileExists(filename))
3184 setup_file_hash = loadSetupFileHash(filename);
3186 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3189 struct dirent *dir_entry;
3190 boolean valid_file_found = FALSE;
3192 if ((dir = opendir(directory_path)) != NULL)
3194 while ((dir_entry = readdir(dir)) != NULL)
3196 char *entry_name = dir_entry->d_name;
3198 if (FileIsArtworkType(entry_name, type))
3200 valid_file_found = TRUE;
3208 if (!valid_file_found)
3210 if (!strEqual(directory_name, "."))
3211 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3213 free(directory_path);
3220 artwork_new = newTreeInfo();
3223 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3225 setTreeInfoToDefaults(artwork_new, type);
3227 artwork_new->subdir = getStringCopy(directory_name);
3229 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3232 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3235 /* set all structure fields according to the token/value pairs */
3237 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3238 setSetupInfo(levelinfo_tokens, i,
3239 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3242 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3243 setString(&artwork_new->name, artwork_new->subdir);
3245 if (artwork_new->identifier == NULL)
3246 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3248 if (artwork_new->name_sorting == NULL)
3249 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3252 if (node_parent == NULL) /* top level group */
3254 artwork_new->basepath = getStringCopy(base_directory);
3255 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3257 else /* sub level group */
3259 artwork_new->basepath = getStringCopy(node_parent->basepath);
3260 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3263 artwork_new->in_user_dir =
3264 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3266 /* (may use ".sort_priority" from "setup_file_hash" above) */
3267 artwork_new->color = ARTWORKCOLOR(artwork_new);
3269 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3271 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3273 if (strEqual(artwork_new->subdir, "."))
3275 if (artwork_new->user_defined)
3277 setString(&artwork_new->identifier, "private");
3278 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3282 setString(&artwork_new->identifier, "classic");
3283 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3286 /* set to new values after changing ".sort_priority" */
3287 artwork_new->color = ARTWORKCOLOR(artwork_new);
3289 setString(&artwork_new->class_desc,
3290 getLevelClassDescription(artwork_new));
3294 setString(&artwork_new->identifier, artwork_new->subdir);
3297 setString(&artwork_new->name, artwork_new->identifier);
3298 setString(&artwork_new->name_sorting, artwork_new->name);
3302 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3305 pushTreeInfo(node_first, artwork_new);
3307 freeSetupFileHash(setup_file_hash);
3309 free(directory_path);
3315 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3316 TreeInfo *node_parent,
3317 char *base_directory, int type)
3320 struct dirent *dir_entry;
3321 boolean valid_entry_found = FALSE;
3323 if ((dir = opendir(base_directory)) == NULL)
3325 /* display error if directory is main "options.graphics_directory" etc. */
3326 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3327 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3332 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3334 struct stat file_status;
3335 char *directory_name = dir_entry->d_name;
3336 char *directory_path = getPath2(base_directory, directory_name);
3338 /* skip directory entries for current and parent directory */
3339 if (strEqual(directory_name, ".") ||
3340 strEqual(directory_name, ".."))
3342 free(directory_path);
3346 /* skip directory entries which are not a directory or are not accessible */
3347 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3348 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3350 free(directory_path);
3354 free(directory_path);
3356 /* check if this directory contains artwork with or without config file */
3357 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3359 directory_name, type);
3364 /* check if this directory directly contains artwork itself */
3365 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3366 base_directory, ".",
3368 if (!valid_entry_found)
3369 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3373 static TreeInfo *getDummyArtworkInfo(int type)
3375 /* this is only needed when there is completely no artwork available */
3376 TreeInfo *artwork_new = newTreeInfo();
3378 setTreeInfoToDefaults(artwork_new, type);
3380 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3381 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3382 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3384 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3385 setString(&artwork_new->name, UNDEFINED_FILENAME);
3386 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3391 void LoadArtworkInfo()
3393 LoadArtworkInfoCache();
3395 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3397 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3398 options.graphics_directory,
3399 TREE_TYPE_GRAPHICS_DIR);
3400 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3401 getUserGraphicsDir(),
3402 TREE_TYPE_GRAPHICS_DIR);
3404 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3405 options.sounds_directory,
3406 TREE_TYPE_SOUNDS_DIR);
3407 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3409 TREE_TYPE_SOUNDS_DIR);
3411 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3412 options.music_directory,
3413 TREE_TYPE_MUSIC_DIR);
3414 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3416 TREE_TYPE_MUSIC_DIR);
3418 if (artwork.gfx_first == NULL)
3419 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3420 if (artwork.snd_first == NULL)
3421 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3422 if (artwork.mus_first == NULL)
3423 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3425 /* before sorting, the first entries will be from the user directory */
3426 artwork.gfx_current =
3427 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3428 if (artwork.gfx_current == NULL)
3429 artwork.gfx_current =
3430 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3431 if (artwork.gfx_current == NULL)
3432 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3434 artwork.snd_current =
3435 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3436 if (artwork.snd_current == NULL)
3437 artwork.snd_current =
3438 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3439 if (artwork.snd_current == NULL)
3440 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3442 artwork.mus_current =
3443 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3444 if (artwork.mus_current == NULL)
3445 artwork.mus_current =
3446 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3447 if (artwork.mus_current == NULL)
3448 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3450 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3451 artwork.snd_current_identifier = artwork.snd_current->identifier;
3452 artwork.mus_current_identifier = artwork.mus_current->identifier;
3455 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3456 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3457 printf("music set == %s\n\n", artwork.mus_current_identifier);
3460 sortTreeInfo(&artwork.gfx_first);
3461 sortTreeInfo(&artwork.snd_first);
3462 sortTreeInfo(&artwork.mus_first);
3465 dumpTreeInfo(artwork.gfx_first, 0);
3466 dumpTreeInfo(artwork.snd_first, 0);
3467 dumpTreeInfo(artwork.mus_first, 0);
3471 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3472 LevelDirTree *level_node)
3474 static unsigned long progress_delay = 0;
3475 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3476 int type = (*artwork_node)->type;
3478 /* recursively check all level directories for artwork sub-directories */
3482 /* check all tree entries for artwork, but skip parent link entries */
3483 if (!level_node->parent_link)
3485 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3486 boolean cached = (artwork_new != NULL);
3490 pushTreeInfo(artwork_node, artwork_new);
3494 TreeInfo *topnode_last = *artwork_node;
3495 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3496 ARTWORK_DIRECTORY(type));
3498 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3500 if (topnode_last != *artwork_node) /* check for newly added node */
3502 artwork_new = *artwork_node;
3504 setString(&artwork_new->identifier, level_node->subdir);
3505 setString(&artwork_new->name, level_node->name);
3506 setString(&artwork_new->name_sorting, level_node->name_sorting);
3508 artwork_new->sort_priority = level_node->sort_priority;
3509 artwork_new->color = LEVELCOLOR(artwork_new);
3515 /* insert artwork info (from old cache or filesystem) into new cache */
3516 if (artwork_new != NULL)
3517 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3521 if (level_node->level_group ||
3522 DelayReached(&progress_delay, progress_delay_value))
3523 DrawInitText(level_node->name, 150, FC_YELLOW);
3526 if (level_node->node_group != NULL)
3527 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3529 level_node = level_node->next;
3533 void LoadLevelArtworkInfo()
3535 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3537 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3538 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3539 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3541 SaveArtworkInfoCache();
3543 /* needed for reloading level artwork not known at ealier stage */
3545 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3547 artwork.gfx_current =
3548 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3549 if (artwork.gfx_current == NULL)
3550 artwork.gfx_current =
3551 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3552 if (artwork.gfx_current == NULL)
3553 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3556 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3558 artwork.snd_current =
3559 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3560 if (artwork.snd_current == NULL)
3561 artwork.snd_current =
3562 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3563 if (artwork.snd_current == NULL)
3564 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3567 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3569 artwork.mus_current =
3570 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3571 if (artwork.mus_current == NULL)
3572 artwork.mus_current =
3573 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3574 if (artwork.mus_current == NULL)
3575 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3578 sortTreeInfo(&artwork.gfx_first);
3579 sortTreeInfo(&artwork.snd_first);
3580 sortTreeInfo(&artwork.mus_first);
3583 dumpTreeInfo(artwork.gfx_first, 0);
3584 dumpTreeInfo(artwork.snd_first, 0);
3585 dumpTreeInfo(artwork.mus_first, 0);
3589 static void SaveUserLevelInfo()
3591 LevelDirTree *level_info;
3596 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3598 if (!(file = fopen(filename, MODE_WRITE)))
3600 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3605 level_info = newTreeInfo();
3607 /* always start with reliable default values */
3608 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3610 setString(&level_info->name, getLoginName());
3611 setString(&level_info->author, getRealName());
3612 level_info->levels = 100;
3613 level_info->first_level = 1;
3615 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3617 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3618 getCookie("LEVELINFO")));
3621 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3623 if (i == LEVELINFO_TOKEN_NAME ||
3624 i == LEVELINFO_TOKEN_AUTHOR ||
3625 i == LEVELINFO_TOKEN_LEVELS ||
3626 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3627 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3629 /* just to make things nicer :) */
3630 if (i == LEVELINFO_TOKEN_AUTHOR)
3631 fprintf(file, "\n");
3634 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3638 SetFilePermissions(filename, PERMS_PRIVATE);
3640 freeTreeInfo(level_info);
3644 char *getSetupValue(int type, void *value)
3646 static char value_string[MAX_LINE_LEN];
3654 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3658 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3662 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3666 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3670 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3674 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3678 sprintf(value_string, "%d", *(int *)value);
3682 if (*(char **)value == NULL)
3685 strcpy(value_string, *(char **)value);
3689 value_string[0] = '\0';
3693 if (type & TYPE_GHOSTED)
3694 strcpy(value_string, "n/a");
3696 return value_string;
3699 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3703 static char token_string[MAX_LINE_LEN];
3704 int token_type = token_info[token_nr].type;
3705 void *setup_value = token_info[token_nr].value;
3706 char *token_text = token_info[token_nr].text;
3707 char *value_string = getSetupValue(token_type, setup_value);
3709 /* build complete token string */
3710 sprintf(token_string, "%s%s", prefix, token_text);
3712 /* build setup entry line */
3713 line = getFormattedSetupEntry(token_string, value_string);
3715 if (token_type == TYPE_KEY_X11)
3717 Key key = *(Key *)setup_value;
3718 char *keyname = getKeyNameFromKey(key);
3720 /* add comment, if useful */
3721 if (!strEqual(keyname, "(undefined)") &&
3722 !strEqual(keyname, "(unknown)"))
3724 /* add at least one whitespace */
3726 for (i = strlen(line); i < token_comment_position; i++)
3730 strcat(line, keyname);
3737 void LoadLevelSetup_LastSeries()
3739 /* ----------------------------------------------------------------------- */
3740 /* ~/.<program>/levelsetup.conf */
3741 /* ----------------------------------------------------------------------- */
3743 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3744 SetupFileHash *level_setup_hash = NULL;
3746 /* always start with reliable default values */
3747 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3749 if ((level_setup_hash = loadSetupFileHash(filename)))
3751 char *last_level_series =
3752 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3754 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3756 if (leveldir_current == NULL)
3757 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3759 checkSetupFileHashIdentifier(level_setup_hash, filename,
3760 getCookie("LEVELSETUP"));
3762 freeSetupFileHash(level_setup_hash);
3765 Error(ERR_WARN, "using default setup values");
3770 void SaveLevelSetup_LastSeries()
3772 /* ----------------------------------------------------------------------- */
3773 /* ~/.<program>/levelsetup.conf */
3774 /* ----------------------------------------------------------------------- */
3776 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3777 char *level_subdir = leveldir_current->subdir;
3780 InitUserDataDirectory();
3782 if (!(file = fopen(filename, MODE_WRITE)))
3784 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3789 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3790 getCookie("LEVELSETUP")));
3791 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3796 SetFilePermissions(filename, PERMS_PRIVATE);
3801 static void checkSeriesInfo()
3803 static char *level_directory = NULL;
3805 struct dirent *dir_entry;
3807 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3809 level_directory = getPath2((leveldir_current->in_user_dir ?
3810 getUserLevelDir(NULL) :
3811 options.level_directory),
3812 leveldir_current->fullpath);
3814 if ((dir = opendir(level_directory)) == NULL)
3816 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3820 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3822 if (strlen(dir_entry->d_name) > 4 &&
3823 dir_entry->d_name[3] == '.' &&
3824 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3826 char levelnum_str[4];
3829 strncpy(levelnum_str, dir_entry->d_name, 3);
3830 levelnum_str[3] = '\0';
3832 levelnum_value = atoi(levelnum_str);
3835 if (levelnum_value < leveldir_current->first_level)
3837 Error(ERR_WARN, "additional level %d found", levelnum_value);
3838 leveldir_current->first_level = levelnum_value;
3840 else if (levelnum_value > leveldir_current->last_level)
3842 Error(ERR_WARN, "additional level %d found", levelnum_value);
3843 leveldir_current->last_level = levelnum_value;
3852 void LoadLevelSetup_SeriesInfo()
3855 SetupFileHash *level_setup_hash = NULL;
3856 char *level_subdir = leveldir_current->subdir;
3858 /* always start with reliable default values */
3859 level_nr = leveldir_current->first_level;
3861 checkSeriesInfo(leveldir_current);
3863 /* ----------------------------------------------------------------------- */
3864 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3865 /* ----------------------------------------------------------------------- */
3867 level_subdir = leveldir_current->subdir;
3869 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3871 if ((level_setup_hash = loadSetupFileHash(filename)))
3875 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3879 level_nr = atoi(token_value);
3881 if (level_nr < leveldir_current->first_level)
3882 level_nr = leveldir_current->first_level;
3883 if (level_nr > leveldir_current->last_level)
3884 level_nr = leveldir_current->last_level;
3887 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3891 int level_nr = atoi(token_value);
3893 if (level_nr < leveldir_current->first_level)
3894 level_nr = leveldir_current->first_level;
3895 if (level_nr > leveldir_current->last_level + 1)
3896 level_nr = leveldir_current->last_level;
3898 if (leveldir_current->user_defined || !leveldir_current->handicap)
3899 level_nr = leveldir_current->last_level;
3901 leveldir_current->handicap_level = level_nr;
3904 checkSetupFileHashIdentifier(level_setup_hash, filename,
3905 getCookie("LEVELSETUP"));
3907 freeSetupFileHash(level_setup_hash);
3910 Error(ERR_WARN, "using default setup values");
3915 void SaveLevelSetup_SeriesInfo()
3918 char *level_subdir = leveldir_current->subdir;
3919 char *level_nr_str = int2str(level_nr, 0);
3920 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3923 /* ----------------------------------------------------------------------- */
3924 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3925 /* ----------------------------------------------------------------------- */
3927 InitLevelSetupDirectory(level_subdir);
3929 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3931 if (!(file = fopen(filename, MODE_WRITE)))
3933 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3938 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3939 getCookie("LEVELSETUP")));
3940 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3942 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3943 handicap_level_str));
3947 SetFilePermissions(filename, PERMS_PRIVATE);