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 *getClassicArtworkSet(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 *getClassicArtworkDir(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));
341 No (or non-existing) artwork configured in "levelinfo.conf". This would
342 normally result in using the artwork configured in the setup menu. But
343 if an artwork subdirectory exists (which might contain custom artwork
344 or an artwork configuration file), this level artwork must be treated
345 as relative to the default "classic" artwork, not to the artwork that
346 is currently configured in the setup menu.
348 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
349 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
350 the real "classic" artwork from the original R'n'D (like "gfx_classic").
353 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
355 checked_free(*artwork_set_ptr);
359 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
360 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
364 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
365 *artwork_set_ptr = NULL;
371 return *artwork_set_ptr;
374 inline static char *getLevelArtworkSet(int type)
376 if (leveldir_current == NULL)
379 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
382 inline static char *getLevelArtworkDir(int type)
384 if (leveldir_current == NULL)
385 return UNDEFINED_FILENAME;
387 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
390 char *getTapeFilename(int nr)
392 static char *filename = NULL;
393 char basename[MAX_FILENAME_LEN];
395 checked_free(filename);
397 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
398 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
403 char *getSolutionTapeFilename(int nr)
405 static char *filename = NULL;
406 char basename[MAX_FILENAME_LEN];
408 checked_free(filename);
410 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
411 filename = getPath2(getSolutionTapeDir(), basename);
416 char *getScoreFilename(int nr)
418 static char *filename = NULL;
419 char basename[MAX_FILENAME_LEN];
421 checked_free(filename);
423 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
424 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
429 char *getSetupFilename()
431 static char *filename = NULL;
433 checked_free(filename);
435 filename = getPath2(getSetupDir(), SETUP_FILENAME);
440 char *getEditorSetupFilename()
442 static char *filename = NULL;
444 checked_free(filename);
445 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
447 if (fileExists(filename))
450 checked_free(filename);
451 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
456 char *getHelpAnimFilename()
458 static char *filename = NULL;
460 checked_free(filename);
462 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
467 char *getHelpTextFilename()
469 static char *filename = NULL;
471 checked_free(filename);
473 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
478 char *getLevelSetInfoFilename()
480 static char *filename = NULL;
495 for (i = 0; basenames[i] != NULL; i++)
497 checked_free(filename);
498 filename = getPath2(getCurrentLevelDir(), basenames[i]);
500 if (fileExists(filename))
507 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
509 static char basename[32];
511 sprintf(basename, "%s_%d.txt",
512 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
517 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
519 static char *filename = NULL;
521 boolean skip_setup_artwork = FALSE;
523 checked_free(filename);
525 basename = getLevelSetTitleMessageBasename(nr, initial);
527 if (!gfx.override_level_graphics)
529 /* 1st try: look for special artwork in current level series directory */
530 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
531 if (fileExists(filename))
536 /* 2nd try: look for message file in current level set directory */
537 filename = getPath2(getCurrentLevelDir(), basename);
538 if (fileExists(filename))
543 /* check if there is special artwork configured in level series config */
544 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
546 /* 3rd try: look for special artwork configured in level series config */
547 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
548 if (fileExists(filename))
553 /* take missing artwork configured in level set config from default */
554 skip_setup_artwork = TRUE;
558 if (!skip_setup_artwork)
560 /* 4th try: look for special artwork in configured artwork directory */
561 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
562 if (fileExists(filename))
568 /* 5th try: look for default artwork in new default artwork directory */
569 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
570 if (fileExists(filename))
575 /* 6th try: look for default artwork in old default artwork directory */
576 filename = getPath2(options.graphics_directory, basename);
577 if (fileExists(filename))
580 return NULL; /* cannot find specified artwork file anywhere */
583 static char *getCorrectedArtworkBasename(char *basename)
585 char *basename_corrected = basename;
587 #if defined(PLATFORM_MSDOS)
588 if (program.filename_prefix != NULL)
590 int prefix_len = strlen(program.filename_prefix);
592 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
593 basename_corrected = &basename[prefix_len];
595 /* if corrected filename is still longer than standard MS-DOS filename
596 size (8 characters + 1 dot + 3 characters file extension), shorten
597 filename by writing file extension after 8th basename character */
598 if (strlen(basename_corrected) > 8 + 1 + 3)
600 static char *msdos_filename = NULL;
602 checked_free(msdos_filename);
604 msdos_filename = getStringCopy(basename_corrected);
605 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
607 basename_corrected = msdos_filename;
612 return basename_corrected;
615 char *getCustomImageFilename(char *basename)
617 static char *filename = NULL;
618 boolean skip_setup_artwork = FALSE;
620 checked_free(filename);
622 basename = getCorrectedArtworkBasename(basename);
624 if (!gfx.override_level_graphics)
626 /* 1st try: look for special artwork in current level series directory */
627 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
628 if (fileExists(filename))
633 /* check if there is special artwork configured in level series config */
634 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
636 /* 2nd try: look for special artwork configured in level series config */
637 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
638 if (fileExists(filename))
643 /* take missing artwork configured in level set config from default */
644 skip_setup_artwork = TRUE;
648 if (!skip_setup_artwork)
650 /* 3rd try: look for special artwork in configured artwork directory */
651 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
652 if (fileExists(filename))
658 /* 4th try: look for default artwork in new default artwork directory */
659 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
660 if (fileExists(filename))
665 /* 5th try: look for default artwork in old default artwork directory */
666 filename = getPath2(options.graphics_directory, basename);
667 if (fileExists(filename))
670 #if defined(CREATE_SPECIAL_EDITION)
674 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
676 /* 6th try: look for fallback artwork in old default artwork directory */
677 /* (needed to prevent errors when trying to access unused artwork files) */
678 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
679 if (fileExists(filename))
683 return NULL; /* cannot find specified artwork file anywhere */
686 char *getCustomSoundFilename(char *basename)
688 static char *filename = NULL;
689 boolean skip_setup_artwork = FALSE;
691 checked_free(filename);
693 basename = getCorrectedArtworkBasename(basename);
695 if (!gfx.override_level_sounds)
697 /* 1st try: look for special artwork in current level series directory */
698 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
699 if (fileExists(filename))
704 /* check if there is special artwork configured in level series config */
705 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
707 /* 2nd try: look for special artwork configured in level series config */
708 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
709 if (fileExists(filename))
714 /* take missing artwork configured in level set config from default */
715 skip_setup_artwork = TRUE;
719 if (!skip_setup_artwork)
721 /* 3rd try: look for special artwork in configured artwork directory */
722 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
723 if (fileExists(filename))
729 /* 4th try: look for default artwork in new default artwork directory */
730 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
731 if (fileExists(filename))
736 /* 5th try: look for default artwork in old default artwork directory */
737 filename = getPath2(options.sounds_directory, basename);
738 if (fileExists(filename))
741 #if defined(CREATE_SPECIAL_EDITION)
745 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
747 /* 6th try: look for fallback artwork in old default artwork directory */
748 /* (needed to prevent errors when trying to access unused artwork files) */
749 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
750 if (fileExists(filename))
754 return NULL; /* cannot find specified artwork file anywhere */
757 char *getCustomMusicFilename(char *basename)
759 static char *filename = NULL;
760 boolean skip_setup_artwork = FALSE;
762 checked_free(filename);
764 basename = getCorrectedArtworkBasename(basename);
766 if (!gfx.override_level_music)
768 /* 1st try: look for special artwork in current level series directory */
769 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
770 if (fileExists(filename))
775 /* check if there is special artwork configured in level series config */
776 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
778 /* 2nd try: look for special artwork configured in level series config */
779 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
780 if (fileExists(filename))
785 /* take missing artwork configured in level set config from default */
786 skip_setup_artwork = TRUE;
790 if (!skip_setup_artwork)
792 /* 3rd try: look for special artwork in configured artwork directory */
793 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
794 if (fileExists(filename))
800 /* 4th try: look for default artwork in new default artwork directory */
801 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
802 if (fileExists(filename))
807 /* 5th try: look for default artwork in old default artwork directory */
808 filename = getPath2(options.music_directory, basename);
809 if (fileExists(filename))
812 #if defined(CREATE_SPECIAL_EDITION)
816 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
818 /* 6th try: look for fallback artwork in old default artwork directory */
819 /* (needed to prevent errors when trying to access unused artwork files) */
820 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
821 if (fileExists(filename))
825 return NULL; /* cannot find specified artwork file anywhere */
828 char *getCustomArtworkFilename(char *basename, int type)
830 if (type == ARTWORK_TYPE_GRAPHICS)
831 return getCustomImageFilename(basename);
832 else if (type == ARTWORK_TYPE_SOUNDS)
833 return getCustomSoundFilename(basename);
834 else if (type == ARTWORK_TYPE_MUSIC)
835 return getCustomMusicFilename(basename);
837 return UNDEFINED_FILENAME;
840 char *getCustomArtworkConfigFilename(int type)
842 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
845 char *getCustomArtworkLevelConfigFilename(int type)
847 static char *filename = NULL;
849 checked_free(filename);
851 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
856 char *getCustomMusicDirectory(void)
858 static char *directory = NULL;
859 boolean skip_setup_artwork = FALSE;
861 checked_free(directory);
863 if (!gfx.override_level_music)
865 /* 1st try: look for special artwork in current level series directory */
866 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
867 if (fileExists(directory))
872 /* check if there is special artwork configured in level series config */
873 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
875 /* 2nd try: look for special artwork configured in level series config */
876 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
877 if (fileExists(directory))
882 /* take missing artwork configured in level set config from default */
883 skip_setup_artwork = TRUE;
887 if (!skip_setup_artwork)
889 /* 3rd try: look for special artwork in configured artwork directory */
890 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
891 if (fileExists(directory))
897 /* 4th try: look for default artwork in new default artwork directory */
898 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
899 if (fileExists(directory))
904 /* 5th try: look for default artwork in old default artwork directory */
905 directory = getStringCopy(options.music_directory);
906 if (fileExists(directory))
909 return NULL; /* cannot find specified artwork file anywhere */
912 void InitTapeDirectory(char *level_subdir)
914 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
915 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
916 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
919 void InitScoreDirectory(char *level_subdir)
921 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
922 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
923 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
926 static void SaveUserLevelInfo();
928 void InitUserLevelDirectory(char *level_subdir)
930 if (!fileExists(getUserLevelDir(level_subdir)))
932 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
933 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
934 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
940 void InitLevelSetupDirectory(char *level_subdir)
942 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
943 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
944 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
947 void InitCacheDirectory()
949 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
950 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
954 /* ------------------------------------------------------------------------- */
955 /* some functions to handle lists of level and artwork directories */
956 /* ------------------------------------------------------------------------- */
958 TreeInfo *newTreeInfo()
960 return checked_calloc(sizeof(TreeInfo));
963 TreeInfo *newTreeInfo_setDefaults(int type)
965 TreeInfo *ti = newTreeInfo();
967 setTreeInfoToDefaults(ti, type);
972 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
974 node_new->next = *node_first;
975 *node_first = node_new;
978 int numTreeInfo(TreeInfo *node)
991 boolean validLevelSeries(TreeInfo *node)
993 return (node != NULL && !node->node_group && !node->parent_link);
996 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1001 if (node->node_group) /* enter level group (step down into tree) */
1002 return getFirstValidTreeInfoEntry(node->node_group);
1003 else if (node->parent_link) /* skip start entry of level group */
1005 if (node->next) /* get first real level series entry */
1006 return getFirstValidTreeInfoEntry(node->next);
1007 else /* leave empty level group and go on */
1008 return getFirstValidTreeInfoEntry(node->node_parent->next);
1010 else /* this seems to be a regular level series */
1014 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1019 if (node->node_parent == NULL) /* top level group */
1020 return *node->node_top;
1021 else /* sub level group */
1022 return node->node_parent->node_group;
1025 int numTreeInfoInGroup(TreeInfo *node)
1027 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1030 int posTreeInfo(TreeInfo *node)
1032 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1037 if (node_cmp == node)
1041 node_cmp = node_cmp->next;
1047 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1049 TreeInfo *node_default = node;
1061 return node_default;
1064 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1066 if (identifier == NULL)
1071 if (node->node_group)
1073 TreeInfo *node_group;
1075 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1080 else if (!node->parent_link)
1082 if (strEqual(identifier, node->identifier))
1092 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1093 TreeInfo *node, boolean skip_sets_without_levels)
1100 if (!node->parent_link && !node->level_group &&
1101 skip_sets_without_levels && node->levels == 0)
1102 return cloneTreeNode(node_top, node_parent, node->next,
1103 skip_sets_without_levels);
1106 node_new = getTreeInfoCopy(node); /* copy complete node */
1108 node_new = newTreeInfo();
1110 *node_new = *node; /* copy complete node */
1113 node_new->node_top = node_top; /* correct top node link */
1114 node_new->node_parent = node_parent; /* correct parent node link */
1116 if (node->level_group)
1117 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1118 skip_sets_without_levels);
1120 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1121 skip_sets_without_levels);
1126 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1128 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1130 *ti_new = ti_cloned;
1133 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1135 boolean settings_changed = FALSE;
1139 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1140 !strEqual(node->graphics_set, node->graphics_set_ecs))
1142 setString(&node->graphics_set, node->graphics_set_ecs);
1143 settings_changed = TRUE;
1145 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1146 !strEqual(node->graphics_set, node->graphics_set_aga))
1148 setString(&node->graphics_set, node->graphics_set_aga);
1149 settings_changed = TRUE;
1152 if (node->node_group != NULL)
1153 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1158 return settings_changed;
1161 void dumpTreeInfo(TreeInfo *node, int depth)
1165 printf("Dumping TreeInfo:\n");
1169 for (i = 0; i < (depth + 1) * 3; i++)
1172 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1173 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1175 if (node->node_group != NULL)
1176 dumpTreeInfo(node->node_group, depth + 1);
1182 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1183 int (*compare_function)(const void *,
1186 int num_nodes = numTreeInfo(*node_first);
1187 TreeInfo **sort_array;
1188 TreeInfo *node = *node_first;
1194 /* allocate array for sorting structure pointers */
1195 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1197 /* writing structure pointers to sorting array */
1198 while (i < num_nodes && node) /* double boundary check... */
1200 sort_array[i] = node;
1206 /* sorting the structure pointers in the sorting array */
1207 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1210 /* update the linkage of list elements with the sorted node array */
1211 for (i = 0; i < num_nodes - 1; i++)
1212 sort_array[i]->next = sort_array[i + 1];
1213 sort_array[num_nodes - 1]->next = NULL;
1215 /* update the linkage of the main list anchor pointer */
1216 *node_first = sort_array[0];
1220 /* now recursively sort the level group structures */
1224 if (node->node_group != NULL)
1225 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1231 void sortTreeInfo(TreeInfo **node_first)
1233 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1237 /* ========================================================================= */
1238 /* some stuff from "files.c" */
1239 /* ========================================================================= */
1241 #if defined(PLATFORM_WIN32)
1243 #define S_IRGRP S_IRUSR
1246 #define S_IROTH S_IRUSR
1249 #define S_IWGRP S_IWUSR
1252 #define S_IWOTH S_IWUSR
1255 #define S_IXGRP S_IXUSR
1258 #define S_IXOTH S_IXUSR
1261 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1266 #endif /* PLATFORM_WIN32 */
1268 /* file permissions for newly written files */
1269 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1270 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1271 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1273 #define MODE_W_PRIVATE (S_IWUSR)
1274 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1275 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1277 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1278 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1280 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1281 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1285 static char *dir = NULL;
1287 #if defined(PLATFORM_WIN32)
1290 dir = checked_malloc(MAX_PATH + 1);
1292 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1295 #elif defined(PLATFORM_UNIX)
1298 if ((dir = getenv("HOME")) == NULL)
1302 if ((pwd = getpwuid(getuid())) != NULL)
1303 dir = getStringCopy(pwd->pw_dir);
1315 char *getCommonDataDir(void)
1317 static char *common_data_dir = NULL;
1319 #if defined(PLATFORM_WIN32)
1320 if (common_data_dir == NULL)
1322 char *dir = checked_malloc(MAX_PATH + 1);
1324 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1325 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1326 common_data_dir = getPath2(dir, program.userdata_subdir);
1328 common_data_dir = options.rw_base_directory;
1331 if (common_data_dir == NULL)
1332 common_data_dir = options.rw_base_directory;
1335 return common_data_dir;
1338 char *getPersonalDataDir(void)
1340 static char *personal_data_dir = NULL;
1342 #if defined(PLATFORM_MACOSX)
1343 if (personal_data_dir == NULL)
1344 personal_data_dir = getPath2(getHomeDir(), "Documents");
1346 if (personal_data_dir == NULL)
1347 personal_data_dir = getHomeDir();
1350 return personal_data_dir;
1353 char *getUserGameDataDir(void)
1355 static char *user_game_data_dir = NULL;
1357 if (user_game_data_dir == NULL)
1358 user_game_data_dir = getPath2(getPersonalDataDir(),
1359 program.userdata_subdir);
1361 return user_game_data_dir;
1364 void updateUserGameDataDir()
1366 #if defined(PLATFORM_MACOSX)
1367 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1368 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1370 /* convert old Unix style game data directory to Mac OS X style, if needed */
1371 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1373 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1375 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1376 userdata_dir_old, userdata_dir_new);
1378 /* continue using Unix style data directory -- this should not happen */
1379 program.userdata_path = getPath2(getPersonalDataDir(),
1380 program.userdata_subdir_unix);
1384 free(userdata_dir_old);
1390 return getUserGameDataDir();
1393 static mode_t posix_umask(mode_t mask)
1395 #if defined(PLATFORM_UNIX)
1402 static int posix_mkdir(const char *pathname, mode_t mode)
1404 #if defined(PLATFORM_WIN32)
1405 return mkdir(pathname);
1407 return mkdir(pathname, mode);
1411 void createDirectory(char *dir, char *text, int permission_class)
1413 /* leave "other" permissions in umask untouched, but ensure group parts
1414 of USERDATA_DIR_MODE are not masked */
1415 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1416 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1417 mode_t normal_umask = posix_umask(0);
1418 mode_t group_umask = ~(dir_mode & S_IRWXG);
1419 posix_umask(normal_umask & group_umask);
1421 if (!fileExists(dir))
1422 if (posix_mkdir(dir, dir_mode) != 0)
1423 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1425 posix_umask(normal_umask); /* reset normal umask */
1428 void InitUserDataDirectory()
1430 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1433 void SetFilePermissions(char *filename, int permission_class)
1435 chmod(filename, (permission_class == PERMS_PRIVATE ?
1436 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1439 char *getCookie(char *file_type)
1441 static char cookie[MAX_COOKIE_LEN + 1];
1443 if (strlen(program.cookie_prefix) + 1 +
1444 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1445 return "[COOKIE ERROR]"; /* should never happen */
1447 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1448 program.cookie_prefix, file_type,
1449 program.version_major, program.version_minor);
1454 int getFileVersionFromCookieString(const char *cookie)
1456 const char *ptr_cookie1, *ptr_cookie2;
1457 const char *pattern1 = "_FILE_VERSION_";
1458 const char *pattern2 = "?.?";
1459 const int len_cookie = strlen(cookie);
1460 const int len_pattern1 = strlen(pattern1);
1461 const int len_pattern2 = strlen(pattern2);
1462 const int len_pattern = len_pattern1 + len_pattern2;
1463 int version_major, version_minor;
1465 if (len_cookie <= len_pattern)
1468 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1469 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1471 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1474 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1475 ptr_cookie2[1] != '.' ||
1476 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1479 version_major = ptr_cookie2[0] - '0';
1480 version_minor = ptr_cookie2[2] - '0';
1482 return VERSION_IDENT(version_major, version_minor, 0, 0);
1485 boolean checkCookieString(const char *cookie, const char *template)
1487 const char *pattern = "_FILE_VERSION_?.?";
1488 const int len_cookie = strlen(cookie);
1489 const int len_template = strlen(template);
1490 const int len_pattern = strlen(pattern);
1492 if (len_cookie != len_template)
1495 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1501 /* ------------------------------------------------------------------------- */
1502 /* setup file list and hash handling functions */
1503 /* ------------------------------------------------------------------------- */
1505 char *getFormattedSetupEntry(char *token, char *value)
1508 static char entry[MAX_LINE_LEN];
1510 /* if value is an empty string, just return token without value */
1514 /* start with the token and some spaces to format output line */
1515 sprintf(entry, "%s:", token);
1516 for (i = strlen(entry); i < token_value_position; i++)
1519 /* continue with the token's value */
1520 strcat(entry, value);
1525 SetupFileList *newSetupFileList(char *token, char *value)
1527 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1529 new->token = getStringCopy(token);
1530 new->value = getStringCopy(value);
1537 void freeSetupFileList(SetupFileList *list)
1542 checked_free(list->token);
1543 checked_free(list->value);
1546 freeSetupFileList(list->next);
1551 char *getListEntry(SetupFileList *list, char *token)
1556 if (strEqual(list->token, token))
1559 return getListEntry(list->next, token);
1562 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1567 if (strEqual(list->token, token))
1569 checked_free(list->value);
1571 list->value = getStringCopy(value);
1575 else if (list->next == NULL)
1576 return (list->next = newSetupFileList(token, value));
1578 return setListEntry(list->next, token, value);
1581 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1586 if (list->next == NULL)
1587 return (list->next = newSetupFileList(token, value));
1589 return addListEntry(list->next, token, value);
1593 static void printSetupFileList(SetupFileList *list)
1598 printf("token: '%s'\n", list->token);
1599 printf("value: '%s'\n", list->value);
1601 printSetupFileList(list->next);
1606 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1607 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1608 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1609 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1611 #define insert_hash_entry hashtable_insert
1612 #define search_hash_entry hashtable_search
1613 #define change_hash_entry hashtable_change
1614 #define remove_hash_entry hashtable_remove
1617 static unsigned int get_hash_from_key(void *key)
1622 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1623 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1624 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1625 it works better than many other constants, prime or not) has never been
1626 adequately explained.
1628 If you just want to have a good hash function, and cannot wait, djb2
1629 is one of the best string hash functions i know. It has excellent
1630 distribution and speed on many different sets of keys and table sizes.
1631 You are not likely to do better with one of the "well known" functions
1632 such as PJW, K&R, etc.
1634 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1637 char *str = (char *)key;
1638 unsigned int hash = 5381;
1641 while ((c = *str++))
1642 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1647 static int keys_are_equal(void *key1, void *key2)
1649 return (strEqual((char *)key1, (char *)key2));
1652 SetupFileHash *newSetupFileHash()
1654 SetupFileHash *new_hash =
1655 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1657 if (new_hash == NULL)
1658 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1663 void freeSetupFileHash(SetupFileHash *hash)
1668 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1671 char *getHashEntry(SetupFileHash *hash, char *token)
1676 return search_hash_entry(hash, token);
1679 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1686 value_copy = getStringCopy(value);
1688 /* change value; if it does not exist, insert it as new */
1689 if (!change_hash_entry(hash, token, value_copy))
1690 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1691 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1694 char *removeHashEntry(SetupFileHash *hash, char *token)
1699 return remove_hash_entry(hash, token);
1703 static void printSetupFileHash(SetupFileHash *hash)
1705 BEGIN_HASH_ITERATION(hash, itr)
1707 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1708 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1710 END_HASH_ITERATION(hash, itr)
1714 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1715 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1716 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1718 static boolean token_value_separator_found = FALSE;
1719 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1720 static boolean token_value_separator_warning = FALSE;
1722 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1723 static boolean token_already_exists_warning = FALSE;
1726 static boolean getTokenValueFromSetupLineExt(char *line,
1727 char **token_ptr, char **value_ptr,
1728 char *filename, char *line_raw,
1730 boolean separator_required)
1732 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1733 char *token, *value, *line_ptr;
1735 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1736 if (line_raw == NULL)
1738 strncpy(line_copy, line, MAX_LINE_LEN);
1739 line_copy[MAX_LINE_LEN] = '\0';
1742 strcpy(line_raw_copy, line_copy);
1743 line_raw = line_raw_copy;
1746 /* cut trailing comment from input line */
1747 for (line_ptr = line; *line_ptr; line_ptr++)
1749 if (*line_ptr == '#')
1756 /* cut trailing whitespaces from input line */
1757 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1758 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1761 /* ignore empty lines */
1765 /* cut leading whitespaces from token */
1766 for (token = line; *token; token++)
1767 if (*token != ' ' && *token != '\t')
1770 /* start with empty value as reliable default */
1773 token_value_separator_found = FALSE;
1775 /* find end of token to determine start of value */
1776 for (line_ptr = token; *line_ptr; line_ptr++)
1779 /* first look for an explicit token/value separator, like ':' or '=' */
1780 if (*line_ptr == ':' || *line_ptr == '=')
1782 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1785 *line_ptr = '\0'; /* terminate token string */
1786 value = line_ptr + 1; /* set beginning of value */
1788 token_value_separator_found = TRUE;
1794 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1795 /* fallback: if no token/value separator found, also allow whitespaces */
1796 if (!token_value_separator_found && !separator_required)
1798 for (line_ptr = token; *line_ptr; line_ptr++)
1800 if (*line_ptr == ' ' || *line_ptr == '\t')
1802 *line_ptr = '\0'; /* terminate token string */
1803 value = line_ptr + 1; /* set beginning of value */
1805 token_value_separator_found = TRUE;
1811 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1812 if (token_value_separator_found)
1814 if (!token_value_separator_warning)
1816 Error(ERR_INFO_LINE, "-");
1818 if (filename != NULL)
1820 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1821 Error(ERR_INFO, "- config file: '%s'", filename);
1825 Error(ERR_WARN, "missing token/value separator(s):");
1828 token_value_separator_warning = TRUE;
1831 if (filename != NULL)
1832 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1834 Error(ERR_INFO, "- line: '%s'", line_raw);
1840 /* cut trailing whitespaces from token */
1841 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1842 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1845 /* cut leading whitespaces from value */
1846 for (; *value; value++)
1847 if (*value != ' ' && *value != '\t')
1852 value = "true"; /* treat tokens without value as "true" */
1861 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1863 /* while the internal (old) interface does not require a token/value
1864 separator (for downwards compatibility with existing files which
1865 don't use them), it is mandatory for the external (new) interface */
1867 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1871 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1872 boolean top_recursion_level, boolean is_hash)
1874 static SetupFileHash *include_filename_hash = NULL;
1875 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1876 char *token, *value, *line_ptr;
1877 void *insert_ptr = NULL;
1878 boolean read_continued_line = FALSE;
1880 int line_nr = 0, token_count = 0, include_count = 0;
1882 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1883 token_value_separator_warning = FALSE;
1886 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1887 token_already_exists_warning = FALSE;
1890 if (!(file = fopen(filename, MODE_READ)))
1892 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1897 /* use "insert pointer" to store list end for constant insertion complexity */
1899 insert_ptr = setup_file_data;
1901 /* on top invocation, create hash to mark included files (to prevent loops) */
1902 if (top_recursion_level)
1903 include_filename_hash = newSetupFileHash();
1905 /* mark this file as already included (to prevent including it again) */
1906 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1910 /* read next line of input file */
1911 if (!fgets(line, MAX_LINE_LEN, file))
1914 /* check if line was completely read and is terminated by line break */
1915 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1918 /* cut trailing line break (this can be newline and/or carriage return) */
1919 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1920 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1923 /* copy raw input line for later use (mainly debugging output) */
1924 strcpy(line_raw, line);
1926 if (read_continued_line)
1929 /* !!! ??? WHY ??? !!! */
1930 /* cut leading whitespaces from input line */
1931 for (line_ptr = line; *line_ptr; line_ptr++)
1932 if (*line_ptr != ' ' && *line_ptr != '\t')
1936 /* append new line to existing line, if there is enough space */
1937 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1938 strcat(previous_line, line_ptr);
1940 strcpy(line, previous_line); /* copy storage buffer to line */
1942 read_continued_line = FALSE;
1945 /* if the last character is '\', continue at next line */
1946 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1948 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1949 strcpy(previous_line, line); /* copy line to storage buffer */
1951 read_continued_line = TRUE;
1956 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1957 line_raw, line_nr, FALSE))
1962 if (strEqual(token, "include"))
1964 if (getHashEntry(include_filename_hash, value) == NULL)
1966 char *basepath = getBasePath(filename);
1967 char *basename = getBaseName(value);
1968 char *filename_include = getPath2(basepath, basename);
1971 Error(ERR_INFO, "[including file '%s']", filename_include);
1974 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
1978 free(filename_include);
1984 Error(ERR_WARN, "ignoring already processed file '%s'", value);
1991 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1993 getHashEntry((SetupFileHash *)setup_file_data, token);
1995 if (old_value != NULL)
1997 if (!token_already_exists_warning)
1999 Error(ERR_INFO_LINE, "-");
2000 Error(ERR_WARN, "duplicate token(s) found in config file:");
2001 Error(ERR_INFO, "- config file: '%s'", filename);
2003 token_already_exists_warning = TRUE;
2006 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2007 Error(ERR_INFO, " old value: '%s'", old_value);
2008 Error(ERR_INFO, " new value: '%s'", value);
2012 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2016 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2026 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2027 if (token_value_separator_warning)
2028 Error(ERR_INFO_LINE, "-");
2031 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2032 if (token_already_exists_warning)
2033 Error(ERR_INFO_LINE, "-");
2036 if (token_count == 0 && include_count == 0)
2037 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2039 if (top_recursion_level)
2040 freeSetupFileHash(include_filename_hash);
2047 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2048 boolean top_recursion_level, boolean is_hash)
2050 static SetupFileHash *include_filename_hash = NULL;
2051 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2052 char *token, *value, *line_ptr;
2053 void *insert_ptr = NULL;
2054 boolean read_continued_line = FALSE;
2057 int token_count = 0;
2059 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2060 token_value_separator_warning = FALSE;
2063 if (!(file = fopen(filename, MODE_READ)))
2065 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2070 /* use "insert pointer" to store list end for constant insertion complexity */
2072 insert_ptr = setup_file_data;
2074 /* on top invocation, create hash to mark included files (to prevent loops) */
2075 if (top_recursion_level)
2076 include_filename_hash = newSetupFileHash();
2078 /* mark this file as already included (to prevent including it again) */
2079 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2083 /* read next line of input file */
2084 if (!fgets(line, MAX_LINE_LEN, file))
2087 /* check if line was completely read and is terminated by line break */
2088 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2091 /* cut trailing line break (this can be newline and/or carriage return) */
2092 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2093 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2096 /* copy raw input line for later use (mainly debugging output) */
2097 strcpy(line_raw, line);
2099 if (read_continued_line)
2101 /* cut leading whitespaces from input line */
2102 for (line_ptr = line; *line_ptr; line_ptr++)
2103 if (*line_ptr != ' ' && *line_ptr != '\t')
2106 /* append new line to existing line, if there is enough space */
2107 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2108 strcat(previous_line, line_ptr);
2110 strcpy(line, previous_line); /* copy storage buffer to line */
2112 read_continued_line = FALSE;
2115 /* if the last character is '\', continue at next line */
2116 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2118 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2119 strcpy(previous_line, line); /* copy line to storage buffer */
2121 read_continued_line = TRUE;
2126 /* cut trailing comment from input line */
2127 for (line_ptr = line; *line_ptr; line_ptr++)
2129 if (*line_ptr == '#')
2136 /* cut trailing whitespaces from input line */
2137 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2138 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2141 /* ignore empty lines */
2145 /* cut leading whitespaces from token */
2146 for (token = line; *token; token++)
2147 if (*token != ' ' && *token != '\t')
2150 /* start with empty value as reliable default */
2153 token_value_separator_found = FALSE;
2155 /* find end of token to determine start of value */
2156 for (line_ptr = token; *line_ptr; line_ptr++)
2159 /* first look for an explicit token/value separator, like ':' or '=' */
2160 if (*line_ptr == ':' || *line_ptr == '=')
2162 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2165 *line_ptr = '\0'; /* terminate token string */
2166 value = line_ptr + 1; /* set beginning of value */
2168 token_value_separator_found = TRUE;
2174 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2175 /* fallback: if no token/value separator found, also allow whitespaces */
2176 if (!token_value_separator_found)
2178 for (line_ptr = token; *line_ptr; line_ptr++)
2180 if (*line_ptr == ' ' || *line_ptr == '\t')
2182 *line_ptr = '\0'; /* terminate token string */
2183 value = line_ptr + 1; /* set beginning of value */
2185 token_value_separator_found = TRUE;
2191 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2192 if (token_value_separator_found)
2194 if (!token_value_separator_warning)
2196 Error(ERR_INFO_LINE, "-");
2197 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2198 Error(ERR_INFO, "- config file: '%s'", filename);
2200 token_value_separator_warning = TRUE;
2203 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2209 /* cut trailing whitespaces from token */
2210 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2211 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2214 /* cut leading whitespaces from value */
2215 for (; *value; value++)
2216 if (*value != ' ' && *value != '\t')
2221 value = "true"; /* treat tokens without value as "true" */
2226 if (strEqual(token, "include"))
2228 if (getHashEntry(include_filename_hash, value) == NULL)
2230 char *basepath = getBasePath(filename);
2231 char *basename = getBaseName(value);
2232 char *filename_include = getPath2(basepath, basename);
2235 Error(ERR_INFO, "[including file '%s']", filename_include);
2238 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2242 free(filename_include);
2246 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2252 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2254 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2263 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2264 if (token_value_separator_warning)
2265 Error(ERR_INFO_LINE, "-");
2268 if (token_count == 0)
2269 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2271 if (top_recursion_level)
2272 freeSetupFileHash(include_filename_hash);
2278 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2282 if (!(file = fopen(filename, MODE_WRITE)))
2284 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2289 BEGIN_HASH_ITERATION(hash, itr)
2291 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2292 HASH_ITERATION_VALUE(itr)));
2294 END_HASH_ITERATION(hash, itr)
2299 SetupFileList *loadSetupFileList(char *filename)
2301 SetupFileList *setup_file_list = newSetupFileList("", "");
2302 SetupFileList *first_valid_list_entry;
2304 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2306 freeSetupFileList(setup_file_list);
2311 first_valid_list_entry = setup_file_list->next;
2313 /* free empty list header */
2314 setup_file_list->next = NULL;
2315 freeSetupFileList(setup_file_list);
2317 return first_valid_list_entry;
2320 SetupFileHash *loadSetupFileHash(char *filename)
2322 SetupFileHash *setup_file_hash = newSetupFileHash();
2324 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2326 freeSetupFileHash(setup_file_hash);
2331 return setup_file_hash;
2334 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2335 char *filename, char *identifier)
2337 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2340 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2341 else if (!checkCookieString(value, identifier))
2342 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2346 /* ========================================================================= */
2347 /* setup file stuff */
2348 /* ========================================================================= */
2350 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2351 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2352 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2354 /* level directory info */
2355 #define LEVELINFO_TOKEN_IDENTIFIER 0
2356 #define LEVELINFO_TOKEN_NAME 1
2357 #define LEVELINFO_TOKEN_NAME_SORTING 2
2358 #define LEVELINFO_TOKEN_AUTHOR 3
2359 #define LEVELINFO_TOKEN_YEAR 4
2360 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2361 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2362 #define LEVELINFO_TOKEN_TESTED_BY 7
2363 #define LEVELINFO_TOKEN_LEVELS 8
2364 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2365 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2366 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2367 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2368 #define LEVELINFO_TOKEN_READONLY 13
2369 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2370 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2371 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2372 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2373 #define LEVELINFO_TOKEN_MUSIC_SET 18
2374 #define LEVELINFO_TOKEN_FILENAME 19
2375 #define LEVELINFO_TOKEN_FILETYPE 20
2376 #define LEVELINFO_TOKEN_HANDICAP 21
2377 #define LEVELINFO_TOKEN_SKIP_LEVELS 22
2379 #define NUM_LEVELINFO_TOKENS 23
2381 static LevelDirTree ldi;
2383 static struct TokenInfo levelinfo_tokens[] =
2385 /* level directory info */
2386 { TYPE_STRING, &ldi.identifier, "identifier" },
2387 { TYPE_STRING, &ldi.name, "name" },
2388 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2389 { TYPE_STRING, &ldi.author, "author" },
2390 { TYPE_STRING, &ldi.year, "year" },
2391 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2392 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2393 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2394 { TYPE_INTEGER, &ldi.levels, "levels" },
2395 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2396 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2397 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2398 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2399 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2400 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2401 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2402 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2403 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2404 { TYPE_STRING, &ldi.music_set, "music_set" },
2405 { TYPE_STRING, &ldi.level_filename, "filename" },
2406 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2407 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2408 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2411 static struct TokenInfo artworkinfo_tokens[] =
2413 /* artwork directory info */
2414 { TYPE_STRING, &ldi.identifier, "identifier" },
2415 { TYPE_STRING, &ldi.subdir, "subdir" },
2416 { TYPE_STRING, &ldi.name, "name" },
2417 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2418 { TYPE_STRING, &ldi.author, "author" },
2419 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2420 { TYPE_STRING, &ldi.basepath, "basepath" },
2421 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2422 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2423 { TYPE_INTEGER, &ldi.color, "color" },
2424 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2429 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2433 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2434 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2435 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2436 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2439 ti->node_parent = NULL;
2440 ti->node_group = NULL;
2447 ti->fullpath = NULL;
2448 ti->basepath = NULL;
2449 ti->identifier = NULL;
2450 ti->name = getStringCopy(ANONYMOUS_NAME);
2451 ti->name_sorting = NULL;
2452 ti->author = getStringCopy(ANONYMOUS_NAME);
2455 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2456 ti->latest_engine = FALSE; /* default: get from level */
2457 ti->parent_link = FALSE;
2458 ti->in_user_dir = FALSE;
2459 ti->user_defined = FALSE;
2461 ti->class_desc = NULL;
2463 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2465 if (ti->type == TREE_TYPE_LEVEL_DIR)
2467 ti->imported_from = NULL;
2468 ti->imported_by = NULL;
2469 ti->tested_by = NULL;
2471 ti->graphics_set_ecs = NULL;
2472 ti->graphics_set_aga = NULL;
2473 ti->graphics_set = NULL;
2474 ti->sounds_set = NULL;
2475 ti->music_set = NULL;
2476 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2477 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2478 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2480 ti->level_filename = NULL;
2481 ti->level_filetype = NULL;
2484 ti->first_level = 0;
2486 ti->level_group = FALSE;
2487 ti->handicap_level = 0;
2488 ti->readonly = TRUE;
2489 ti->handicap = TRUE;
2490 ti->skip_levels = FALSE;
2494 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2498 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2500 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2505 /* copy all values from the parent structure */
2507 ti->type = parent->type;
2509 ti->node_top = parent->node_top;
2510 ti->node_parent = parent;
2511 ti->node_group = NULL;
2518 ti->fullpath = NULL;
2519 ti->basepath = NULL;
2520 ti->identifier = NULL;
2521 ti->name = getStringCopy(ANONYMOUS_NAME);
2522 ti->name_sorting = NULL;
2523 ti->author = getStringCopy(parent->author);
2524 ti->year = getStringCopy(parent->year);
2526 ti->sort_priority = parent->sort_priority;
2527 ti->latest_engine = parent->latest_engine;
2528 ti->parent_link = FALSE;
2529 ti->in_user_dir = parent->in_user_dir;
2530 ti->user_defined = parent->user_defined;
2531 ti->color = parent->color;
2532 ti->class_desc = getStringCopy(parent->class_desc);
2534 ti->infotext = getStringCopy(parent->infotext);
2536 if (ti->type == TREE_TYPE_LEVEL_DIR)
2538 ti->imported_from = getStringCopy(parent->imported_from);
2539 ti->imported_by = getStringCopy(parent->imported_by);
2540 ti->tested_by = getStringCopy(parent->tested_by);
2542 ti->graphics_set_ecs = NULL;
2543 ti->graphics_set_aga = NULL;
2544 ti->graphics_set = NULL;
2545 ti->sounds_set = NULL;
2546 ti->music_set = NULL;
2547 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2548 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2549 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2551 ti->level_filename = NULL;
2552 ti->level_filetype = NULL;
2555 ti->first_level = 0;
2557 ti->level_group = FALSE;
2558 ti->handicap_level = 0;
2559 ti->readonly = TRUE;
2560 ti->handicap = TRUE;
2561 ti->skip_levels = FALSE;
2565 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2567 TreeInfo *ti_copy = newTreeInfo();
2569 /* copy all values from the original structure */
2571 ti_copy->type = ti->type;
2573 ti_copy->node_top = ti->node_top;
2574 ti_copy->node_parent = ti->node_parent;
2575 ti_copy->node_group = ti->node_group;
2576 ti_copy->next = ti->next;
2578 ti_copy->cl_first = ti->cl_first;
2579 ti_copy->cl_cursor = ti->cl_cursor;
2581 ti_copy->subdir = getStringCopy(ti->subdir);
2582 ti_copy->fullpath = getStringCopy(ti->fullpath);
2583 ti_copy->basepath = getStringCopy(ti->basepath);
2584 ti_copy->identifier = getStringCopy(ti->identifier);
2585 ti_copy->name = getStringCopy(ti->name);
2586 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2587 ti_copy->author = getStringCopy(ti->author);
2588 ti_copy->year = getStringCopy(ti->year);
2589 ti_copy->imported_from = getStringCopy(ti->imported_from);
2590 ti_copy->imported_by = getStringCopy(ti->imported_by);
2591 ti_copy->tested_by = getStringCopy(ti->tested_by);
2593 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2594 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2595 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2596 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2597 ti_copy->music_set = getStringCopy(ti->music_set);
2598 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2599 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2600 ti_copy->music_path = getStringCopy(ti->music_path);
2602 ti_copy->level_filename = getStringCopy(ti->level_filename);
2603 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2605 ti_copy->levels = ti->levels;
2606 ti_copy->first_level = ti->first_level;
2607 ti_copy->last_level = ti->last_level;
2608 ti_copy->sort_priority = ti->sort_priority;
2610 ti_copy->latest_engine = ti->latest_engine;
2612 ti_copy->level_group = ti->level_group;
2613 ti_copy->parent_link = ti->parent_link;
2614 ti_copy->in_user_dir = ti->in_user_dir;
2615 ti_copy->user_defined = ti->user_defined;
2616 ti_copy->readonly = ti->readonly;
2617 ti_copy->handicap = ti->handicap;
2618 ti_copy->skip_levels = ti->skip_levels;
2620 ti_copy->color = ti->color;
2621 ti_copy->class_desc = getStringCopy(ti->class_desc);
2622 ti_copy->handicap_level = ti->handicap_level;
2624 ti_copy->infotext = getStringCopy(ti->infotext);
2629 static void freeTreeInfo(TreeInfo *ti)
2634 checked_free(ti->subdir);
2635 checked_free(ti->fullpath);
2636 checked_free(ti->basepath);
2637 checked_free(ti->identifier);
2639 checked_free(ti->name);
2640 checked_free(ti->name_sorting);
2641 checked_free(ti->author);
2642 checked_free(ti->year);
2644 checked_free(ti->class_desc);
2646 checked_free(ti->infotext);
2648 if (ti->type == TREE_TYPE_LEVEL_DIR)
2650 checked_free(ti->imported_from);
2651 checked_free(ti->imported_by);
2652 checked_free(ti->tested_by);
2654 checked_free(ti->graphics_set_ecs);
2655 checked_free(ti->graphics_set_aga);
2656 checked_free(ti->graphics_set);
2657 checked_free(ti->sounds_set);
2658 checked_free(ti->music_set);
2660 checked_free(ti->graphics_path);
2661 checked_free(ti->sounds_path);
2662 checked_free(ti->music_path);
2664 checked_free(ti->level_filename);
2665 checked_free(ti->level_filetype);
2671 void setSetupInfo(struct TokenInfo *token_info,
2672 int token_nr, char *token_value)
2674 int token_type = token_info[token_nr].type;
2675 void *setup_value = token_info[token_nr].value;
2677 if (token_value == NULL)
2680 /* set setup field to corresponding token value */
2685 *(boolean *)setup_value = get_boolean_from_string(token_value);
2689 *(Key *)setup_value = getKeyFromKeyName(token_value);
2693 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2697 *(int *)setup_value = get_integer_from_string(token_value);
2701 checked_free(*(char **)setup_value);
2702 *(char **)setup_value = getStringCopy(token_value);
2710 static int compareTreeInfoEntries(const void *object1, const void *object2)
2712 const TreeInfo *entry1 = *((TreeInfo **)object1);
2713 const TreeInfo *entry2 = *((TreeInfo **)object2);
2714 int class_sorting1, class_sorting2;
2717 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2719 class_sorting1 = LEVELSORTING(entry1);
2720 class_sorting2 = LEVELSORTING(entry2);
2724 class_sorting1 = ARTWORKSORTING(entry1);
2725 class_sorting2 = ARTWORKSORTING(entry2);
2728 if (entry1->parent_link || entry2->parent_link)
2729 compare_result = (entry1->parent_link ? -1 : +1);
2730 else if (entry1->sort_priority == entry2->sort_priority)
2732 char *name1 = getStringToLower(entry1->name_sorting);
2733 char *name2 = getStringToLower(entry2->name_sorting);
2735 compare_result = strcmp(name1, name2);
2740 else if (class_sorting1 == class_sorting2)
2741 compare_result = entry1->sort_priority - entry2->sort_priority;
2743 compare_result = class_sorting1 - class_sorting2;
2745 return compare_result;
2748 static void createParentTreeInfoNode(TreeInfo *node_parent)
2752 if (node_parent == NULL)
2755 ti_new = newTreeInfo();
2756 setTreeInfoToDefaults(ti_new, node_parent->type);
2758 ti_new->node_parent = node_parent;
2759 ti_new->parent_link = TRUE;
2761 setString(&ti_new->identifier, node_parent->identifier);
2762 setString(&ti_new->name, ".. (parent directory)");
2763 setString(&ti_new->name_sorting, ti_new->name);
2765 setString(&ti_new->subdir, "..");
2766 setString(&ti_new->fullpath, node_parent->fullpath);
2768 ti_new->sort_priority = node_parent->sort_priority;
2769 ti_new->latest_engine = node_parent->latest_engine;
2771 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2773 pushTreeInfo(&node_parent->node_group, ti_new);
2777 /* -------------------------------------------------------------------------- */
2778 /* functions for handling level and custom artwork info cache */
2779 /* -------------------------------------------------------------------------- */
2781 static void LoadArtworkInfoCache()
2783 InitCacheDirectory();
2785 if (artworkinfo_cache_old == NULL)
2787 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2789 /* try to load artwork info hash from already existing cache file */
2790 artworkinfo_cache_old = loadSetupFileHash(filename);
2792 /* if no artwork info cache file was found, start with empty hash */
2793 if (artworkinfo_cache_old == NULL)
2794 artworkinfo_cache_old = newSetupFileHash();
2799 if (artworkinfo_cache_new == NULL)
2800 artworkinfo_cache_new = newSetupFileHash();
2803 static void SaveArtworkInfoCache()
2805 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2807 InitCacheDirectory();
2809 saveSetupFileHash(artworkinfo_cache_new, filename);
2814 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2816 static char *prefix = NULL;
2818 checked_free(prefix);
2820 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2825 /* (identical to above function, but separate string buffer needed -- nasty) */
2826 static char *getCacheToken(char *prefix, char *suffix)
2828 static char *token = NULL;
2830 checked_free(token);
2832 token = getStringCat2WithSeparator(prefix, suffix, ".");
2837 static char *getFileTimestamp(char *filename)
2839 struct stat file_status;
2841 if (stat(filename, &file_status) != 0) /* cannot stat file */
2842 return getStringCopy(i_to_a(0));
2844 return getStringCopy(i_to_a(file_status.st_mtime));
2847 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2849 struct stat file_status;
2851 if (timestamp_string == NULL)
2854 if (stat(filename, &file_status) != 0) /* cannot stat file */
2857 return (file_status.st_mtime != atoi(timestamp_string));
2860 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2862 char *identifier = level_node->subdir;
2863 char *type_string = ARTWORK_DIRECTORY(type);
2864 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2865 char *token_main = getCacheToken(token_prefix, "CACHED");
2866 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2867 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2868 TreeInfo *artwork_info = NULL;
2870 if (!use_artworkinfo_cache)
2877 artwork_info = newTreeInfo();
2878 setTreeInfoToDefaults(artwork_info, type);
2880 /* set all structure fields according to the token/value pairs */
2881 ldi = *artwork_info;
2882 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2884 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2885 char *value = getHashEntry(artworkinfo_cache_old, token);
2887 setSetupInfo(artworkinfo_tokens, i, value);
2889 /* check if cache entry for this item is invalid or incomplete */
2893 Error(ERR_WARN, "cache entry '%s' invalid", token);
2900 *artwork_info = ldi;
2905 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2906 LEVELINFO_FILENAME);
2907 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2908 ARTWORKINFO_FILENAME(type));
2910 /* check if corresponding "levelinfo.conf" file has changed */
2911 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2912 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2914 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2917 /* check if corresponding "<artworkinfo>.conf" file has changed */
2918 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2919 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2921 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2926 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2929 checked_free(filename_levelinfo);
2930 checked_free(filename_artworkinfo);
2933 if (!cached && artwork_info != NULL)
2935 freeTreeInfo(artwork_info);
2940 return artwork_info;
2943 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2944 LevelDirTree *level_node, int type)
2946 char *identifier = level_node->subdir;
2947 char *type_string = ARTWORK_DIRECTORY(type);
2948 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2949 char *token_main = getCacheToken(token_prefix, "CACHED");
2950 boolean set_cache_timestamps = TRUE;
2953 setHashEntry(artworkinfo_cache_new, token_main, "true");
2955 if (set_cache_timestamps)
2957 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2958 LEVELINFO_FILENAME);
2959 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2960 ARTWORKINFO_FILENAME(type));
2961 char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo);
2962 char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo);
2964 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2965 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2967 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2968 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2970 checked_free(filename_levelinfo);
2971 checked_free(filename_artworkinfo);
2972 checked_free(timestamp_levelinfo);
2973 checked_free(timestamp_artworkinfo);
2976 ldi = *artwork_info;
2977 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2979 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2980 char *value = getSetupValue(artworkinfo_tokens[i].type,
2981 artworkinfo_tokens[i].value);
2983 setHashEntry(artworkinfo_cache_new, token, value);
2988 /* -------------------------------------------------------------------------- */
2989 /* functions for loading level info and custom artwork info */
2990 /* -------------------------------------------------------------------------- */
2992 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2993 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2995 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2996 TreeInfo *node_parent,
2997 char *level_directory,
2998 char *directory_name)
3001 static unsigned long progress_delay = 0;
3002 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3004 char *directory_path = getPath2(level_directory, directory_name);
3005 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3006 SetupFileHash *setup_file_hash;
3007 LevelDirTree *leveldir_new = NULL;
3010 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3011 if (!options.debug && !fileExists(filename))
3013 free(directory_path);
3019 setup_file_hash = loadSetupFileHash(filename);
3021 if (setup_file_hash == NULL)
3023 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3025 free(directory_path);
3031 leveldir_new = newTreeInfo();
3034 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3036 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3038 leveldir_new->subdir = getStringCopy(directory_name);
3040 checkSetupFileHashIdentifier(setup_file_hash, filename,
3041 getCookie("LEVELINFO"));
3043 /* set all structure fields according to the token/value pairs */
3044 ldi = *leveldir_new;
3045 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3046 setSetupInfo(levelinfo_tokens, i,
3047 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3048 *leveldir_new = ldi;
3050 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3051 setString(&leveldir_new->name, leveldir_new->subdir);
3053 if (leveldir_new->identifier == NULL)
3054 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3056 if (leveldir_new->name_sorting == NULL)
3057 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3059 if (node_parent == NULL) /* top level group */
3061 leveldir_new->basepath = getStringCopy(level_directory);
3062 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3064 else /* sub level group */
3066 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3067 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3071 if (leveldir_new->levels < 1)
3072 leveldir_new->levels = 1;
3075 leveldir_new->last_level =
3076 leveldir_new->first_level + leveldir_new->levels - 1;
3078 leveldir_new->in_user_dir =
3079 (!strEqual(leveldir_new->basepath, options.level_directory));
3081 /* adjust some settings if user's private level directory was detected */
3082 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3083 leveldir_new->in_user_dir &&
3084 (strEqual(leveldir_new->subdir, getLoginName()) ||
3085 strEqual(leveldir_new->name, getLoginName()) ||
3086 strEqual(leveldir_new->author, getRealName())))
3088 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3089 leveldir_new->readonly = FALSE;
3092 leveldir_new->user_defined =
3093 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3095 leveldir_new->color = LEVELCOLOR(leveldir_new);
3097 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3099 leveldir_new->handicap_level = /* set handicap to default value */
3100 (leveldir_new->user_defined || !leveldir_new->handicap ?
3101 leveldir_new->last_level : leveldir_new->first_level);
3105 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3106 leveldir_new->level_group);
3108 if (leveldir_new->level_group ||
3109 DelayReached(&progress_delay, progress_delay_value))
3110 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3113 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3117 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3119 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3121 /* skip level sets without levels (which are probably artwork base sets) */
3123 freeSetupFileHash(setup_file_hash);
3124 free(directory_path);
3132 pushTreeInfo(node_first, leveldir_new);
3134 freeSetupFileHash(setup_file_hash);
3136 if (leveldir_new->level_group)
3138 /* create node to link back to current level directory */
3139 createParentTreeInfoNode(leveldir_new);
3141 /* recursively step into sub-directory and look for more level series */
3142 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3143 leveldir_new, directory_path);
3146 free(directory_path);
3152 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3153 TreeInfo *node_parent,
3154 char *level_directory)
3157 struct dirent *dir_entry;
3158 boolean valid_entry_found = FALSE;
3160 if ((dir = opendir(level_directory)) == NULL)
3162 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3166 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3168 struct stat file_status;
3169 char *directory_name = dir_entry->d_name;
3170 char *directory_path = getPath2(level_directory, directory_name);
3172 /* skip entries for current and parent directory */
3173 if (strEqual(directory_name, ".") ||
3174 strEqual(directory_name, ".."))
3176 free(directory_path);
3180 /* find out if directory entry is itself a directory */
3181 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3182 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3184 free(directory_path);
3188 free(directory_path);
3190 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3191 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3192 strEqual(directory_name, MUSIC_DIRECTORY))
3195 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3202 /* special case: top level directory may directly contain "levelinfo.conf" */
3203 if (node_parent == NULL && !valid_entry_found)
3205 /* check if this directory directly contains a file "levelinfo.conf" */
3206 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3207 level_directory, ".");
3210 if (!valid_entry_found)
3211 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3215 boolean AdjustGraphicsForEMC()
3217 boolean settings_changed = FALSE;
3219 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3220 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3222 return settings_changed;
3225 void LoadLevelInfo()
3227 InitUserLevelDirectory(getLoginName());
3229 DrawInitText("Loading level series", 120, FC_GREEN);
3231 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3232 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3234 /* after loading all level set information, clone the level directory tree
3235 and remove all level sets without levels (these may still contain artwork
3236 to be offered in the setup menu as "custom artwork", and are therefore
3237 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3238 leveldir_first_all = leveldir_first;
3239 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3241 AdjustGraphicsForEMC();
3243 /* before sorting, the first entries will be from the user directory */
3244 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3246 if (leveldir_first == NULL)
3247 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3249 sortTreeInfo(&leveldir_first);
3252 dumpTreeInfo(leveldir_first, 0);
3256 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3257 TreeInfo *node_parent,
3258 char *base_directory,
3259 char *directory_name, int type)
3261 char *directory_path = getPath2(base_directory, directory_name);
3262 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3263 SetupFileHash *setup_file_hash = NULL;
3264 TreeInfo *artwork_new = NULL;
3267 if (fileExists(filename))
3268 setup_file_hash = loadSetupFileHash(filename);
3270 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3273 struct dirent *dir_entry;
3274 boolean valid_file_found = FALSE;
3276 if ((dir = opendir(directory_path)) != NULL)
3278 while ((dir_entry = readdir(dir)) != NULL)
3280 char *entry_name = dir_entry->d_name;
3282 if (FileIsArtworkType(entry_name, type))
3284 valid_file_found = TRUE;
3292 if (!valid_file_found)
3294 if (!strEqual(directory_name, "."))
3295 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3297 free(directory_path);
3304 artwork_new = newTreeInfo();
3307 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3309 setTreeInfoToDefaults(artwork_new, type);
3311 artwork_new->subdir = getStringCopy(directory_name);
3313 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3316 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3319 /* set all structure fields according to the token/value pairs */
3321 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3322 setSetupInfo(levelinfo_tokens, i,
3323 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3326 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3327 setString(&artwork_new->name, artwork_new->subdir);
3329 if (artwork_new->identifier == NULL)
3330 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3332 if (artwork_new->name_sorting == NULL)
3333 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3336 if (node_parent == NULL) /* top level group */
3338 artwork_new->basepath = getStringCopy(base_directory);
3339 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3341 else /* sub level group */
3343 artwork_new->basepath = getStringCopy(node_parent->basepath);
3344 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3347 artwork_new->in_user_dir =
3348 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3350 /* (may use ".sort_priority" from "setup_file_hash" above) */
3351 artwork_new->color = ARTWORKCOLOR(artwork_new);
3353 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3355 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3357 if (strEqual(artwork_new->subdir, "."))
3359 if (artwork_new->user_defined)
3361 setString(&artwork_new->identifier, "private");
3362 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3366 setString(&artwork_new->identifier, "classic");
3367 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3370 /* set to new values after changing ".sort_priority" */
3371 artwork_new->color = ARTWORKCOLOR(artwork_new);
3373 setString(&artwork_new->class_desc,
3374 getLevelClassDescription(artwork_new));
3378 setString(&artwork_new->identifier, artwork_new->subdir);
3381 setString(&artwork_new->name, artwork_new->identifier);
3382 setString(&artwork_new->name_sorting, artwork_new->name);
3386 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3389 pushTreeInfo(node_first, artwork_new);
3391 freeSetupFileHash(setup_file_hash);
3393 free(directory_path);
3399 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3400 TreeInfo *node_parent,
3401 char *base_directory, int type)
3404 struct dirent *dir_entry;
3405 boolean valid_entry_found = FALSE;
3407 if ((dir = opendir(base_directory)) == NULL)
3409 /* display error if directory is main "options.graphics_directory" etc. */
3410 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3411 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3416 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3418 struct stat file_status;
3419 char *directory_name = dir_entry->d_name;
3420 char *directory_path = getPath2(base_directory, directory_name);
3422 /* skip directory entries for current and parent directory */
3423 if (strEqual(directory_name, ".") ||
3424 strEqual(directory_name, ".."))
3426 free(directory_path);
3430 /* skip directory entries which are not a directory or are not accessible */
3431 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3432 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3434 free(directory_path);
3438 free(directory_path);
3440 /* check if this directory contains artwork with or without config file */
3441 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3443 directory_name, type);
3448 /* check if this directory directly contains artwork itself */
3449 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3450 base_directory, ".",
3452 if (!valid_entry_found)
3453 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3457 static TreeInfo *getDummyArtworkInfo(int type)
3459 /* this is only needed when there is completely no artwork available */
3460 TreeInfo *artwork_new = newTreeInfo();
3462 setTreeInfoToDefaults(artwork_new, type);
3464 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3465 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3466 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3468 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3469 setString(&artwork_new->name, UNDEFINED_FILENAME);
3470 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3475 void LoadArtworkInfo()
3477 LoadArtworkInfoCache();
3479 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3481 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3482 options.graphics_directory,
3483 TREE_TYPE_GRAPHICS_DIR);
3484 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3485 getUserGraphicsDir(),
3486 TREE_TYPE_GRAPHICS_DIR);
3488 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3489 options.sounds_directory,
3490 TREE_TYPE_SOUNDS_DIR);
3491 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3493 TREE_TYPE_SOUNDS_DIR);
3495 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3496 options.music_directory,
3497 TREE_TYPE_MUSIC_DIR);
3498 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3500 TREE_TYPE_MUSIC_DIR);
3502 if (artwork.gfx_first == NULL)
3503 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3504 if (artwork.snd_first == NULL)
3505 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3506 if (artwork.mus_first == NULL)
3507 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3509 /* before sorting, the first entries will be from the user directory */
3510 artwork.gfx_current =
3511 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3512 if (artwork.gfx_current == NULL)
3513 artwork.gfx_current =
3514 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3515 if (artwork.gfx_current == NULL)
3516 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3518 artwork.snd_current =
3519 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3520 if (artwork.snd_current == NULL)
3521 artwork.snd_current =
3522 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3523 if (artwork.snd_current == NULL)
3524 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3526 artwork.mus_current =
3527 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3528 if (artwork.mus_current == NULL)
3529 artwork.mus_current =
3530 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3531 if (artwork.mus_current == NULL)
3532 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3534 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3535 artwork.snd_current_identifier = artwork.snd_current->identifier;
3536 artwork.mus_current_identifier = artwork.mus_current->identifier;
3539 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3540 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3541 printf("music set == %s\n\n", artwork.mus_current_identifier);
3544 sortTreeInfo(&artwork.gfx_first);
3545 sortTreeInfo(&artwork.snd_first);
3546 sortTreeInfo(&artwork.mus_first);
3549 dumpTreeInfo(artwork.gfx_first, 0);
3550 dumpTreeInfo(artwork.snd_first, 0);
3551 dumpTreeInfo(artwork.mus_first, 0);
3555 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3556 LevelDirTree *level_node)
3559 static unsigned long progress_delay = 0;
3560 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3562 int type = (*artwork_node)->type;
3564 /* recursively check all level directories for artwork sub-directories */
3568 /* check all tree entries for artwork, but skip parent link entries */
3569 if (!level_node->parent_link)
3571 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3572 boolean cached = (artwork_new != NULL);
3576 pushTreeInfo(artwork_node, artwork_new);
3580 TreeInfo *topnode_last = *artwork_node;
3581 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3582 ARTWORK_DIRECTORY(type));
3584 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3586 if (topnode_last != *artwork_node) /* check for newly added node */
3588 artwork_new = *artwork_node;
3590 setString(&artwork_new->identifier, level_node->subdir);
3591 setString(&artwork_new->name, level_node->name);
3592 setString(&artwork_new->name_sorting, level_node->name_sorting);
3594 artwork_new->sort_priority = level_node->sort_priority;
3595 artwork_new->color = LEVELCOLOR(artwork_new);
3601 /* insert artwork info (from old cache or filesystem) into new cache */
3602 if (artwork_new != NULL)
3603 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3607 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3608 level_node->level_group);
3610 if (level_node->level_group ||
3611 DelayReached(&progress_delay, progress_delay_value))
3612 DrawInitText(level_node->name, 150, FC_YELLOW);
3615 if (level_node->node_group != NULL)
3616 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3618 level_node = level_node->next;
3622 void LoadLevelArtworkInfo()
3624 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3626 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3627 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3628 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3630 SaveArtworkInfoCache();
3632 /* needed for reloading level artwork not known at ealier stage */
3634 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3636 artwork.gfx_current =
3637 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3638 if (artwork.gfx_current == NULL)
3639 artwork.gfx_current =
3640 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3641 if (artwork.gfx_current == NULL)
3642 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3645 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3647 artwork.snd_current =
3648 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3649 if (artwork.snd_current == NULL)
3650 artwork.snd_current =
3651 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3652 if (artwork.snd_current == NULL)
3653 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3656 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3658 artwork.mus_current =
3659 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3660 if (artwork.mus_current == NULL)
3661 artwork.mus_current =
3662 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3663 if (artwork.mus_current == NULL)
3664 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3667 sortTreeInfo(&artwork.gfx_first);
3668 sortTreeInfo(&artwork.snd_first);
3669 sortTreeInfo(&artwork.mus_first);
3672 dumpTreeInfo(artwork.gfx_first, 0);
3673 dumpTreeInfo(artwork.snd_first, 0);
3674 dumpTreeInfo(artwork.mus_first, 0);
3678 static void SaveUserLevelInfo()
3680 LevelDirTree *level_info;
3685 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3687 if (!(file = fopen(filename, MODE_WRITE)))
3689 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3694 level_info = newTreeInfo();
3696 /* always start with reliable default values */
3697 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3699 setString(&level_info->name, getLoginName());
3700 setString(&level_info->author, getRealName());
3701 level_info->levels = 100;
3702 level_info->first_level = 1;
3704 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3706 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3707 getCookie("LEVELINFO")));
3710 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3712 if (i == LEVELINFO_TOKEN_NAME ||
3713 i == LEVELINFO_TOKEN_AUTHOR ||
3714 i == LEVELINFO_TOKEN_LEVELS ||
3715 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3716 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3718 /* just to make things nicer :) */
3719 if (i == LEVELINFO_TOKEN_AUTHOR)
3720 fprintf(file, "\n");
3723 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3727 SetFilePermissions(filename, PERMS_PRIVATE);
3729 freeTreeInfo(level_info);
3733 char *getSetupValue(int type, void *value)
3735 static char value_string[MAX_LINE_LEN];
3743 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3747 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3751 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3755 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3759 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3763 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3767 sprintf(value_string, "%d", *(int *)value);
3771 if (*(char **)value == NULL)
3774 strcpy(value_string, *(char **)value);
3778 value_string[0] = '\0';
3782 if (type & TYPE_GHOSTED)
3783 strcpy(value_string, "n/a");
3785 return value_string;
3788 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3792 static char token_string[MAX_LINE_LEN];
3793 int token_type = token_info[token_nr].type;
3794 void *setup_value = token_info[token_nr].value;
3795 char *token_text = token_info[token_nr].text;
3796 char *value_string = getSetupValue(token_type, setup_value);
3798 /* build complete token string */
3799 sprintf(token_string, "%s%s", prefix, token_text);
3801 /* build setup entry line */
3802 line = getFormattedSetupEntry(token_string, value_string);
3804 if (token_type == TYPE_KEY_X11)
3806 Key key = *(Key *)setup_value;
3807 char *keyname = getKeyNameFromKey(key);
3809 /* add comment, if useful */
3810 if (!strEqual(keyname, "(undefined)") &&
3811 !strEqual(keyname, "(unknown)"))
3813 /* add at least one whitespace */
3815 for (i = strlen(line); i < token_comment_position; i++)
3819 strcat(line, keyname);
3826 void LoadLevelSetup_LastSeries()
3828 /* ----------------------------------------------------------------------- */
3829 /* ~/.<program>/levelsetup.conf */
3830 /* ----------------------------------------------------------------------- */
3832 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3833 SetupFileHash *level_setup_hash = NULL;
3835 /* always start with reliable default values */
3836 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3838 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3839 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3841 if (leveldir_current == NULL)
3842 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3845 if ((level_setup_hash = loadSetupFileHash(filename)))
3847 char *last_level_series =
3848 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3850 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3852 if (leveldir_current == NULL)
3853 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3855 checkSetupFileHashIdentifier(level_setup_hash, filename,
3856 getCookie("LEVELSETUP"));
3858 freeSetupFileHash(level_setup_hash);
3861 Error(ERR_WARN, "using default setup values");
3866 void SaveLevelSetup_LastSeries()
3868 /* ----------------------------------------------------------------------- */
3869 /* ~/.<program>/levelsetup.conf */
3870 /* ----------------------------------------------------------------------- */
3872 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3873 char *level_subdir = leveldir_current->subdir;
3876 InitUserDataDirectory();
3878 if (!(file = fopen(filename, MODE_WRITE)))
3880 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3885 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3886 getCookie("LEVELSETUP")));
3887 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3892 SetFilePermissions(filename, PERMS_PRIVATE);
3897 static void checkSeriesInfo()
3899 static char *level_directory = NULL;
3901 struct dirent *dir_entry;
3903 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3905 level_directory = getPath2((leveldir_current->in_user_dir ?
3906 getUserLevelDir(NULL) :
3907 options.level_directory),
3908 leveldir_current->fullpath);
3910 if ((dir = opendir(level_directory)) == NULL)
3912 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3916 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3918 if (strlen(dir_entry->d_name) > 4 &&
3919 dir_entry->d_name[3] == '.' &&
3920 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3922 char levelnum_str[4];
3925 strncpy(levelnum_str, dir_entry->d_name, 3);
3926 levelnum_str[3] = '\0';
3928 levelnum_value = atoi(levelnum_str);
3931 if (levelnum_value < leveldir_current->first_level)
3933 Error(ERR_WARN, "additional level %d found", levelnum_value);
3934 leveldir_current->first_level = levelnum_value;
3936 else if (levelnum_value > leveldir_current->last_level)
3938 Error(ERR_WARN, "additional level %d found", levelnum_value);
3939 leveldir_current->last_level = levelnum_value;
3948 void LoadLevelSetup_SeriesInfo()
3951 SetupFileHash *level_setup_hash = NULL;
3952 char *level_subdir = leveldir_current->subdir;
3954 /* always start with reliable default values */
3955 level_nr = leveldir_current->first_level;
3957 checkSeriesInfo(leveldir_current);
3959 /* ----------------------------------------------------------------------- */
3960 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3961 /* ----------------------------------------------------------------------- */
3963 level_subdir = leveldir_current->subdir;
3965 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3967 if ((level_setup_hash = loadSetupFileHash(filename)))
3971 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3975 level_nr = atoi(token_value);
3977 if (level_nr < leveldir_current->first_level)
3978 level_nr = leveldir_current->first_level;
3979 if (level_nr > leveldir_current->last_level)
3980 level_nr = leveldir_current->last_level;
3983 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3987 int level_nr = atoi(token_value);
3989 if (level_nr < leveldir_current->first_level)
3990 level_nr = leveldir_current->first_level;
3991 if (level_nr > leveldir_current->last_level + 1)
3992 level_nr = leveldir_current->last_level;
3994 if (leveldir_current->user_defined || !leveldir_current->handicap)
3995 level_nr = leveldir_current->last_level;
3997 leveldir_current->handicap_level = level_nr;
4000 checkSetupFileHashIdentifier(level_setup_hash, filename,
4001 getCookie("LEVELSETUP"));
4003 freeSetupFileHash(level_setup_hash);
4006 Error(ERR_WARN, "using default setup values");
4011 void SaveLevelSetup_SeriesInfo()
4014 char *level_subdir = leveldir_current->subdir;
4015 char *level_nr_str = int2str(level_nr, 0);
4016 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4019 /* ----------------------------------------------------------------------- */
4020 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4021 /* ----------------------------------------------------------------------- */
4023 InitLevelSetupDirectory(level_subdir);
4025 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4027 if (!(file = fopen(filename, MODE_WRITE)))
4029 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4034 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4035 getCookie("LEVELSETUP")));
4036 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4038 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4039 handicap_level_str));
4043 SetFilePermissions(filename, PERMS_PRIVATE);