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)
2924 static unsigned long progress_delay = 0;
2925 unsigned long progress_delay_value = 100; /* (in milliseconds) */
2927 char *directory_path = getPath2(level_directory, directory_name);
2928 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2929 SetupFileHash *setup_file_hash;
2930 LevelDirTree *leveldir_new = NULL;
2933 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2934 if (!options.debug && !fileExists(filename))
2936 free(directory_path);
2942 setup_file_hash = loadSetupFileHash(filename);
2944 if (setup_file_hash == NULL)
2946 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2948 free(directory_path);
2954 leveldir_new = newTreeInfo();
2957 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2959 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2961 leveldir_new->subdir = getStringCopy(directory_name);
2963 checkSetupFileHashIdentifier(setup_file_hash, filename,
2964 getCookie("LEVELINFO"));
2966 /* set all structure fields according to the token/value pairs */
2967 ldi = *leveldir_new;
2968 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2969 setSetupInfo(levelinfo_tokens, i,
2970 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2971 *leveldir_new = ldi;
2973 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2974 setString(&leveldir_new->name, leveldir_new->subdir);
2976 if (leveldir_new->identifier == NULL)
2977 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2979 if (leveldir_new->name_sorting == NULL)
2980 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2982 if (node_parent == NULL) /* top level group */
2984 leveldir_new->basepath = getStringCopy(level_directory);
2985 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2987 else /* sub level group */
2989 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2990 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2994 if (leveldir_new->levels < 1)
2995 leveldir_new->levels = 1;
2998 leveldir_new->last_level =
2999 leveldir_new->first_level + leveldir_new->levels - 1;
3001 leveldir_new->in_user_dir =
3002 (!strEqual(leveldir_new->basepath, options.level_directory));
3004 /* adjust some settings if user's private level directory was detected */
3005 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3006 leveldir_new->in_user_dir &&
3007 (strEqual(leveldir_new->subdir, getLoginName()) ||
3008 strEqual(leveldir_new->name, getLoginName()) ||
3009 strEqual(leveldir_new->author, getRealName())))
3011 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3012 leveldir_new->readonly = FALSE;
3015 leveldir_new->user_defined =
3016 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3018 leveldir_new->color = LEVELCOLOR(leveldir_new);
3020 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3022 leveldir_new->handicap_level = /* set handicap to default value */
3023 (leveldir_new->user_defined || !leveldir_new->handicap ?
3024 leveldir_new->last_level : leveldir_new->first_level);
3028 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3029 leveldir_new->level_group);
3031 if (leveldir_new->level_group ||
3032 DelayReached(&progress_delay, progress_delay_value))
3033 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3036 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3040 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3042 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3044 /* skip level sets without levels (which are probably artwork base sets) */
3046 freeSetupFileHash(setup_file_hash);
3047 free(directory_path);
3055 pushTreeInfo(node_first, leveldir_new);
3057 freeSetupFileHash(setup_file_hash);
3059 if (leveldir_new->level_group)
3061 /* create node to link back to current level directory */
3062 createParentTreeInfoNode(leveldir_new);
3064 /* recursively step into sub-directory and look for more level series */
3065 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3066 leveldir_new, directory_path);
3069 free(directory_path);
3075 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3076 TreeInfo *node_parent,
3077 char *level_directory)
3080 struct dirent *dir_entry;
3081 boolean valid_entry_found = FALSE;
3083 if ((dir = opendir(level_directory)) == NULL)
3085 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3089 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3091 struct stat file_status;
3092 char *directory_name = dir_entry->d_name;
3093 char *directory_path = getPath2(level_directory, directory_name);
3095 /* skip entries for current and parent directory */
3096 if (strEqual(directory_name, ".") ||
3097 strEqual(directory_name, ".."))
3099 free(directory_path);
3103 /* find out if directory entry is itself a directory */
3104 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3105 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3107 free(directory_path);
3111 free(directory_path);
3113 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3114 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3115 strEqual(directory_name, MUSIC_DIRECTORY))
3118 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3125 /* special case: top level directory may directly contain "levelinfo.conf" */
3126 if (node_parent == NULL && !valid_entry_found)
3128 /* check if this directory directly contains a file "levelinfo.conf" */
3129 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3130 level_directory, ".");
3133 if (!valid_entry_found)
3134 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3138 boolean AdjustGraphicsForEMC()
3140 boolean settings_changed = FALSE;
3142 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3143 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3145 return settings_changed;
3148 void LoadLevelInfo()
3150 InitUserLevelDirectory(getLoginName());
3152 DrawInitText("Loading level series", 120, FC_GREEN);
3154 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3155 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3157 /* after loading all level set information, clone the level directory tree
3158 and remove all level sets without levels (these may still contain artwork
3159 to be offered in the setup menu as "custom artwork", and are therefore
3160 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3161 leveldir_first_all = leveldir_first;
3162 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3164 AdjustGraphicsForEMC();
3166 /* before sorting, the first entries will be from the user directory */
3167 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3169 if (leveldir_first == NULL)
3170 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3172 sortTreeInfo(&leveldir_first);
3175 dumpTreeInfo(leveldir_first, 0);
3179 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3180 TreeInfo *node_parent,
3181 char *base_directory,
3182 char *directory_name, int type)
3184 char *directory_path = getPath2(base_directory, directory_name);
3185 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3186 SetupFileHash *setup_file_hash = NULL;
3187 TreeInfo *artwork_new = NULL;
3190 if (fileExists(filename))
3191 setup_file_hash = loadSetupFileHash(filename);
3193 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3196 struct dirent *dir_entry;
3197 boolean valid_file_found = FALSE;
3199 if ((dir = opendir(directory_path)) != NULL)
3201 while ((dir_entry = readdir(dir)) != NULL)
3203 char *entry_name = dir_entry->d_name;
3205 if (FileIsArtworkType(entry_name, type))
3207 valid_file_found = TRUE;
3215 if (!valid_file_found)
3217 if (!strEqual(directory_name, "."))
3218 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3220 free(directory_path);
3227 artwork_new = newTreeInfo();
3230 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3232 setTreeInfoToDefaults(artwork_new, type);
3234 artwork_new->subdir = getStringCopy(directory_name);
3236 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3239 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3242 /* set all structure fields according to the token/value pairs */
3244 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3245 setSetupInfo(levelinfo_tokens, i,
3246 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3249 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3250 setString(&artwork_new->name, artwork_new->subdir);
3252 if (artwork_new->identifier == NULL)
3253 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3255 if (artwork_new->name_sorting == NULL)
3256 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3259 if (node_parent == NULL) /* top level group */
3261 artwork_new->basepath = getStringCopy(base_directory);
3262 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3264 else /* sub level group */
3266 artwork_new->basepath = getStringCopy(node_parent->basepath);
3267 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3270 artwork_new->in_user_dir =
3271 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3273 /* (may use ".sort_priority" from "setup_file_hash" above) */
3274 artwork_new->color = ARTWORKCOLOR(artwork_new);
3276 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3278 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3280 if (strEqual(artwork_new->subdir, "."))
3282 if (artwork_new->user_defined)
3284 setString(&artwork_new->identifier, "private");
3285 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3289 setString(&artwork_new->identifier, "classic");
3290 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3293 /* set to new values after changing ".sort_priority" */
3294 artwork_new->color = ARTWORKCOLOR(artwork_new);
3296 setString(&artwork_new->class_desc,
3297 getLevelClassDescription(artwork_new));
3301 setString(&artwork_new->identifier, artwork_new->subdir);
3304 setString(&artwork_new->name, artwork_new->identifier);
3305 setString(&artwork_new->name_sorting, artwork_new->name);
3309 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3312 pushTreeInfo(node_first, artwork_new);
3314 freeSetupFileHash(setup_file_hash);
3316 free(directory_path);
3322 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3323 TreeInfo *node_parent,
3324 char *base_directory, int type)
3327 struct dirent *dir_entry;
3328 boolean valid_entry_found = FALSE;
3330 if ((dir = opendir(base_directory)) == NULL)
3332 /* display error if directory is main "options.graphics_directory" etc. */
3333 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3334 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3339 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3341 struct stat file_status;
3342 char *directory_name = dir_entry->d_name;
3343 char *directory_path = getPath2(base_directory, directory_name);
3345 /* skip directory entries for current and parent directory */
3346 if (strEqual(directory_name, ".") ||
3347 strEqual(directory_name, ".."))
3349 free(directory_path);
3353 /* skip directory entries which are not a directory or are not accessible */
3354 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3355 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3357 free(directory_path);
3361 free(directory_path);
3363 /* check if this directory contains artwork with or without config file */
3364 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3366 directory_name, type);
3371 /* check if this directory directly contains artwork itself */
3372 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3373 base_directory, ".",
3375 if (!valid_entry_found)
3376 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3380 static TreeInfo *getDummyArtworkInfo(int type)
3382 /* this is only needed when there is completely no artwork available */
3383 TreeInfo *artwork_new = newTreeInfo();
3385 setTreeInfoToDefaults(artwork_new, type);
3387 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3388 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3389 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3391 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3392 setString(&artwork_new->name, UNDEFINED_FILENAME);
3393 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3398 void LoadArtworkInfo()
3400 LoadArtworkInfoCache();
3402 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3404 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3405 options.graphics_directory,
3406 TREE_TYPE_GRAPHICS_DIR);
3407 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3408 getUserGraphicsDir(),
3409 TREE_TYPE_GRAPHICS_DIR);
3411 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3412 options.sounds_directory,
3413 TREE_TYPE_SOUNDS_DIR);
3414 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3416 TREE_TYPE_SOUNDS_DIR);
3418 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3419 options.music_directory,
3420 TREE_TYPE_MUSIC_DIR);
3421 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3423 TREE_TYPE_MUSIC_DIR);
3425 if (artwork.gfx_first == NULL)
3426 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3427 if (artwork.snd_first == NULL)
3428 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3429 if (artwork.mus_first == NULL)
3430 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3432 /* before sorting, the first entries will be from the user directory */
3433 artwork.gfx_current =
3434 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3435 if (artwork.gfx_current == NULL)
3436 artwork.gfx_current =
3437 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3438 if (artwork.gfx_current == NULL)
3439 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3441 artwork.snd_current =
3442 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3443 if (artwork.snd_current == NULL)
3444 artwork.snd_current =
3445 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3446 if (artwork.snd_current == NULL)
3447 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3449 artwork.mus_current =
3450 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3451 if (artwork.mus_current == NULL)
3452 artwork.mus_current =
3453 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3454 if (artwork.mus_current == NULL)
3455 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3457 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3458 artwork.snd_current_identifier = artwork.snd_current->identifier;
3459 artwork.mus_current_identifier = artwork.mus_current->identifier;
3462 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3463 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3464 printf("music set == %s\n\n", artwork.mus_current_identifier);
3467 sortTreeInfo(&artwork.gfx_first);
3468 sortTreeInfo(&artwork.snd_first);
3469 sortTreeInfo(&artwork.mus_first);
3472 dumpTreeInfo(artwork.gfx_first, 0);
3473 dumpTreeInfo(artwork.snd_first, 0);
3474 dumpTreeInfo(artwork.mus_first, 0);
3478 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3479 LevelDirTree *level_node)
3482 static unsigned long progress_delay = 0;
3483 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3485 int type = (*artwork_node)->type;
3487 /* recursively check all level directories for artwork sub-directories */
3491 /* check all tree entries for artwork, but skip parent link entries */
3492 if (!level_node->parent_link)
3494 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3495 boolean cached = (artwork_new != NULL);
3499 pushTreeInfo(artwork_node, artwork_new);
3503 TreeInfo *topnode_last = *artwork_node;
3504 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3505 ARTWORK_DIRECTORY(type));
3507 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3509 if (topnode_last != *artwork_node) /* check for newly added node */
3511 artwork_new = *artwork_node;
3513 setString(&artwork_new->identifier, level_node->subdir);
3514 setString(&artwork_new->name, level_node->name);
3515 setString(&artwork_new->name_sorting, level_node->name_sorting);
3517 artwork_new->sort_priority = level_node->sort_priority;
3518 artwork_new->color = LEVELCOLOR(artwork_new);
3524 /* insert artwork info (from old cache or filesystem) into new cache */
3525 if (artwork_new != NULL)
3526 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3530 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3531 level_node->level_group);
3533 if (level_node->level_group ||
3534 DelayReached(&progress_delay, progress_delay_value))
3535 DrawInitText(level_node->name, 150, FC_YELLOW);
3538 if (level_node->node_group != NULL)
3539 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3541 level_node = level_node->next;
3545 void LoadLevelArtworkInfo()
3547 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3549 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3550 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3551 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3553 SaveArtworkInfoCache();
3555 /* needed for reloading level artwork not known at ealier stage */
3557 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3559 artwork.gfx_current =
3560 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3561 if (artwork.gfx_current == NULL)
3562 artwork.gfx_current =
3563 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
3564 if (artwork.gfx_current == NULL)
3565 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3568 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3570 artwork.snd_current =
3571 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3572 if (artwork.snd_current == NULL)
3573 artwork.snd_current =
3574 getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
3575 if (artwork.snd_current == NULL)
3576 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3579 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3581 artwork.mus_current =
3582 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3583 if (artwork.mus_current == NULL)
3584 artwork.mus_current =
3585 getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
3586 if (artwork.mus_current == NULL)
3587 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3590 sortTreeInfo(&artwork.gfx_first);
3591 sortTreeInfo(&artwork.snd_first);
3592 sortTreeInfo(&artwork.mus_first);
3595 dumpTreeInfo(artwork.gfx_first, 0);
3596 dumpTreeInfo(artwork.snd_first, 0);
3597 dumpTreeInfo(artwork.mus_first, 0);
3601 static void SaveUserLevelInfo()
3603 LevelDirTree *level_info;
3608 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3610 if (!(file = fopen(filename, MODE_WRITE)))
3612 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3617 level_info = newTreeInfo();
3619 /* always start with reliable default values */
3620 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3622 setString(&level_info->name, getLoginName());
3623 setString(&level_info->author, getRealName());
3624 level_info->levels = 100;
3625 level_info->first_level = 1;
3627 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3629 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3630 getCookie("LEVELINFO")));
3633 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3635 if (i == LEVELINFO_TOKEN_NAME ||
3636 i == LEVELINFO_TOKEN_AUTHOR ||
3637 i == LEVELINFO_TOKEN_LEVELS ||
3638 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3639 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3641 /* just to make things nicer :) */
3642 if (i == LEVELINFO_TOKEN_AUTHOR)
3643 fprintf(file, "\n");
3646 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3650 SetFilePermissions(filename, PERMS_PRIVATE);
3652 freeTreeInfo(level_info);
3656 char *getSetupValue(int type, void *value)
3658 static char value_string[MAX_LINE_LEN];
3666 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3670 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3674 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3678 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3682 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3686 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3690 sprintf(value_string, "%d", *(int *)value);
3694 if (*(char **)value == NULL)
3697 strcpy(value_string, *(char **)value);
3701 value_string[0] = '\0';
3705 if (type & TYPE_GHOSTED)
3706 strcpy(value_string, "n/a");
3708 return value_string;
3711 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3715 static char token_string[MAX_LINE_LEN];
3716 int token_type = token_info[token_nr].type;
3717 void *setup_value = token_info[token_nr].value;
3718 char *token_text = token_info[token_nr].text;
3719 char *value_string = getSetupValue(token_type, setup_value);
3721 /* build complete token string */
3722 sprintf(token_string, "%s%s", prefix, token_text);
3724 /* build setup entry line */
3725 line = getFormattedSetupEntry(token_string, value_string);
3727 if (token_type == TYPE_KEY_X11)
3729 Key key = *(Key *)setup_value;
3730 char *keyname = getKeyNameFromKey(key);
3732 /* add comment, if useful */
3733 if (!strEqual(keyname, "(undefined)") &&
3734 !strEqual(keyname, "(unknown)"))
3736 /* add at least one whitespace */
3738 for (i = strlen(line); i < token_comment_position; i++)
3742 strcat(line, keyname);
3749 void LoadLevelSetup_LastSeries()
3751 /* ----------------------------------------------------------------------- */
3752 /* ~/.<program>/levelsetup.conf */
3753 /* ----------------------------------------------------------------------- */
3755 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3756 SetupFileHash *level_setup_hash = NULL;
3758 /* always start with reliable default values */
3759 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3761 if ((level_setup_hash = loadSetupFileHash(filename)))
3763 char *last_level_series =
3764 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3766 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3768 if (leveldir_current == NULL)
3769 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3771 checkSetupFileHashIdentifier(level_setup_hash, filename,
3772 getCookie("LEVELSETUP"));
3774 freeSetupFileHash(level_setup_hash);
3777 Error(ERR_WARN, "using default setup values");
3782 void SaveLevelSetup_LastSeries()
3784 /* ----------------------------------------------------------------------- */
3785 /* ~/.<program>/levelsetup.conf */
3786 /* ----------------------------------------------------------------------- */
3788 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3789 char *level_subdir = leveldir_current->subdir;
3792 InitUserDataDirectory();
3794 if (!(file = fopen(filename, MODE_WRITE)))
3796 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3801 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3802 getCookie("LEVELSETUP")));
3803 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3808 SetFilePermissions(filename, PERMS_PRIVATE);
3813 static void checkSeriesInfo()
3815 static char *level_directory = NULL;
3817 struct dirent *dir_entry;
3819 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3821 level_directory = getPath2((leveldir_current->in_user_dir ?
3822 getUserLevelDir(NULL) :
3823 options.level_directory),
3824 leveldir_current->fullpath);
3826 if ((dir = opendir(level_directory)) == NULL)
3828 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3832 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3834 if (strlen(dir_entry->d_name) > 4 &&
3835 dir_entry->d_name[3] == '.' &&
3836 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3838 char levelnum_str[4];
3841 strncpy(levelnum_str, dir_entry->d_name, 3);
3842 levelnum_str[3] = '\0';
3844 levelnum_value = atoi(levelnum_str);
3847 if (levelnum_value < leveldir_current->first_level)
3849 Error(ERR_WARN, "additional level %d found", levelnum_value);
3850 leveldir_current->first_level = levelnum_value;
3852 else if (levelnum_value > leveldir_current->last_level)
3854 Error(ERR_WARN, "additional level %d found", levelnum_value);
3855 leveldir_current->last_level = levelnum_value;
3864 void LoadLevelSetup_SeriesInfo()
3867 SetupFileHash *level_setup_hash = NULL;
3868 char *level_subdir = leveldir_current->subdir;
3870 /* always start with reliable default values */
3871 level_nr = leveldir_current->first_level;
3873 checkSeriesInfo(leveldir_current);
3875 /* ----------------------------------------------------------------------- */
3876 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3877 /* ----------------------------------------------------------------------- */
3879 level_subdir = leveldir_current->subdir;
3881 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3883 if ((level_setup_hash = loadSetupFileHash(filename)))
3887 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3891 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)
3896 level_nr = leveldir_current->last_level;
3899 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3903 int level_nr = atoi(token_value);
3905 if (level_nr < leveldir_current->first_level)
3906 level_nr = leveldir_current->first_level;
3907 if (level_nr > leveldir_current->last_level + 1)
3908 level_nr = leveldir_current->last_level;
3910 if (leveldir_current->user_defined || !leveldir_current->handicap)
3911 level_nr = leveldir_current->last_level;
3913 leveldir_current->handicap_level = level_nr;
3916 checkSetupFileHashIdentifier(level_setup_hash, filename,
3917 getCookie("LEVELSETUP"));
3919 freeSetupFileHash(level_setup_hash);
3922 Error(ERR_WARN, "using default setup values");
3927 void SaveLevelSetup_SeriesInfo()
3930 char *level_subdir = leveldir_current->subdir;
3931 char *level_nr_str = int2str(level_nr, 0);
3932 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3935 /* ----------------------------------------------------------------------- */
3936 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3937 /* ----------------------------------------------------------------------- */
3939 InitLevelSetupDirectory(level_subdir);
3941 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3943 if (!(file = fopen(filename, MODE_WRITE)))
3945 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3950 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3951 getCookie("LEVELSETUP")));
3952 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3954 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3955 handicap_level_str));
3959 SetFilePermissions(filename, PERMS_PRIVATE);