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)))
338 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
343 No (or non-existing) artwork configured in "levelinfo.conf". This would
344 normally result in using the artwork configured in the setup menu. But
345 if an artwork subdirectory exists (which might contain custom artwork
346 or an artwork configuration file), this level artwork must be treated
347 as relative to the default "classic" artwork, not to the artwork that
348 is currently configured in the setup menu.
350 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
351 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
352 the real "classic" artwork from the original R'n'D (like "gfx_classic").
355 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
357 checked_free(*artwork_set_ptr);
361 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
362 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
366 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
367 *artwork_set_ptr = NULL;
373 return *artwork_set_ptr;
376 inline static char *getLevelArtworkSet(int type)
378 if (leveldir_current == NULL)
381 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
384 inline static char *getLevelArtworkDir(int type)
386 if (leveldir_current == NULL)
387 return UNDEFINED_FILENAME;
389 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
392 char *getTapeFilename(int nr)
394 static char *filename = NULL;
395 char basename[MAX_FILENAME_LEN];
397 checked_free(filename);
399 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
400 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
405 char *getSolutionTapeFilename(int nr)
407 static char *filename = NULL;
408 char basename[MAX_FILENAME_LEN];
410 checked_free(filename);
412 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
413 filename = getPath2(getSolutionTapeDir(), basename);
415 if (!fileExists(filename))
417 static char *filename_sln = NULL;
419 checked_free(filename_sln);
421 sprintf(basename, "%03d.sln", nr);
422 filename_sln = getPath2(getSolutionTapeDir(), basename);
424 if (fileExists(filename_sln))
431 char *getScoreFilename(int nr)
433 static char *filename = NULL;
434 char basename[MAX_FILENAME_LEN];
436 checked_free(filename);
438 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
439 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
444 char *getSetupFilename()
446 static char *filename = NULL;
448 checked_free(filename);
450 filename = getPath2(getSetupDir(), SETUP_FILENAME);
455 char *getEditorSetupFilename()
457 static char *filename = NULL;
459 checked_free(filename);
460 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
462 if (fileExists(filename))
465 checked_free(filename);
466 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
471 char *getHelpAnimFilename()
473 static char *filename = NULL;
475 checked_free(filename);
477 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
482 char *getHelpTextFilename()
484 static char *filename = NULL;
486 checked_free(filename);
488 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
493 char *getLevelSetInfoFilename()
495 static char *filename = NULL;
510 for (i = 0; basenames[i] != NULL; i++)
512 checked_free(filename);
513 filename = getPath2(getCurrentLevelDir(), basenames[i]);
515 if (fileExists(filename))
522 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
524 static char basename[32];
526 sprintf(basename, "%s_%d.txt",
527 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
532 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
534 static char *filename = NULL;
536 boolean skip_setup_artwork = FALSE;
538 checked_free(filename);
540 basename = getLevelSetTitleMessageBasename(nr, initial);
542 if (!gfx.override_level_graphics)
544 /* 1st try: look for special artwork in current level series directory */
545 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
546 if (fileExists(filename))
551 /* 2nd try: look for message file in current level set directory */
552 filename = getPath2(getCurrentLevelDir(), basename);
553 if (fileExists(filename))
558 /* check if there is special artwork configured in level series config */
559 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
561 /* 3rd try: look for special artwork configured in level series config */
562 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
563 if (fileExists(filename))
568 /* take missing artwork configured in level set config from default */
569 skip_setup_artwork = TRUE;
573 if (!skip_setup_artwork)
575 /* 4th try: look for special artwork in configured artwork directory */
576 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
577 if (fileExists(filename))
583 /* 5th try: look for default artwork in new default artwork directory */
584 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
585 if (fileExists(filename))
590 /* 6th try: look for default artwork in old default artwork directory */
591 filename = getPath2(options.graphics_directory, basename);
592 if (fileExists(filename))
595 return NULL; /* cannot find specified artwork file anywhere */
598 static char *getCorrectedArtworkBasename(char *basename)
600 char *basename_corrected = basename;
602 #if defined(PLATFORM_MSDOS)
603 if (program.filename_prefix != NULL)
605 int prefix_len = strlen(program.filename_prefix);
607 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
608 basename_corrected = &basename[prefix_len];
610 /* if corrected filename is still longer than standard MS-DOS filename
611 size (8 characters + 1 dot + 3 characters file extension), shorten
612 filename by writing file extension after 8th basename character */
613 if (strlen(basename_corrected) > 8 + 1 + 3)
615 static char *msdos_filename = NULL;
617 checked_free(msdos_filename);
619 msdos_filename = getStringCopy(basename_corrected);
620 strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
622 basename_corrected = msdos_filename;
627 return basename_corrected;
630 char *getCustomImageFilename(char *basename)
632 static char *filename = NULL;
633 boolean skip_setup_artwork = FALSE;
635 checked_free(filename);
637 basename = getCorrectedArtworkBasename(basename);
639 if (!gfx.override_level_graphics)
641 /* 1st try: look for special artwork in current level series directory */
642 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
643 if (fileExists(filename))
648 /* check if there is special artwork configured in level series config */
649 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
651 /* 2nd try: look for special artwork configured in level series config */
652 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
653 if (fileExists(filename))
658 /* take missing artwork configured in level set config from default */
659 skip_setup_artwork = TRUE;
663 if (!skip_setup_artwork)
665 /* 3rd try: look for special artwork in configured artwork directory */
666 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
667 if (fileExists(filename))
673 /* 4th try: look for default artwork in new default artwork directory */
674 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
675 if (fileExists(filename))
680 /* 5th try: look for default artwork in old default artwork directory */
681 filename = getPath2(options.graphics_directory, basename);
682 if (fileExists(filename))
685 #if defined(CREATE_SPECIAL_EDITION)
689 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
691 /* 6th try: look for fallback artwork in old default artwork directory */
692 /* (needed to prevent errors when trying to access unused artwork files) */
693 filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
694 if (fileExists(filename))
698 return NULL; /* cannot find specified artwork file anywhere */
701 char *getCustomSoundFilename(char *basename)
703 static char *filename = NULL;
704 boolean skip_setup_artwork = FALSE;
706 checked_free(filename);
708 basename = getCorrectedArtworkBasename(basename);
710 if (!gfx.override_level_sounds)
712 /* 1st try: look for special artwork in current level series directory */
713 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
714 if (fileExists(filename))
719 /* check if there is special artwork configured in level series config */
720 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
722 /* 2nd try: look for special artwork configured in level series config */
723 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
724 if (fileExists(filename))
729 /* take missing artwork configured in level set config from default */
730 skip_setup_artwork = TRUE;
734 if (!skip_setup_artwork)
736 /* 3rd try: look for special artwork in configured artwork directory */
737 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
738 if (fileExists(filename))
744 /* 4th try: look for default artwork in new default artwork directory */
745 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
746 if (fileExists(filename))
751 /* 5th try: look for default artwork in old default artwork directory */
752 filename = getPath2(options.sounds_directory, basename);
753 if (fileExists(filename))
756 #if defined(CREATE_SPECIAL_EDITION)
760 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
762 /* 6th try: look for fallback artwork in old default artwork directory */
763 /* (needed to prevent errors when trying to access unused artwork files) */
764 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
765 if (fileExists(filename))
769 return NULL; /* cannot find specified artwork file anywhere */
772 char *getCustomMusicFilename(char *basename)
774 static char *filename = NULL;
775 boolean skip_setup_artwork = FALSE;
777 checked_free(filename);
779 basename = getCorrectedArtworkBasename(basename);
781 if (!gfx.override_level_music)
783 /* 1st try: look for special artwork in current level series directory */
784 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
785 if (fileExists(filename))
790 /* check if there is special artwork configured in level series config */
791 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
793 /* 2nd try: look for special artwork configured in level series config */
794 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
795 if (fileExists(filename))
800 /* take missing artwork configured in level set config from default */
801 skip_setup_artwork = TRUE;
805 if (!skip_setup_artwork)
807 /* 3rd try: look for special artwork in configured artwork directory */
808 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
809 if (fileExists(filename))
815 /* 4th try: look for default artwork in new default artwork directory */
816 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
817 if (fileExists(filename))
822 /* 5th try: look for default artwork in old default artwork directory */
823 filename = getPath2(options.music_directory, basename);
824 if (fileExists(filename))
827 #if defined(CREATE_SPECIAL_EDITION)
831 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
833 /* 6th try: look for fallback artwork in old default artwork directory */
834 /* (needed to prevent errors when trying to access unused artwork files) */
835 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
836 if (fileExists(filename))
840 return NULL; /* cannot find specified artwork file anywhere */
843 char *getCustomArtworkFilename(char *basename, int type)
845 if (type == ARTWORK_TYPE_GRAPHICS)
846 return getCustomImageFilename(basename);
847 else if (type == ARTWORK_TYPE_SOUNDS)
848 return getCustomSoundFilename(basename);
849 else if (type == ARTWORK_TYPE_MUSIC)
850 return getCustomMusicFilename(basename);
852 return UNDEFINED_FILENAME;
855 char *getCustomArtworkConfigFilename(int type)
857 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
860 char *getCustomArtworkLevelConfigFilename(int type)
862 static char *filename = NULL;
864 checked_free(filename);
866 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
871 char *getCustomMusicDirectory(void)
873 static char *directory = NULL;
874 boolean skip_setup_artwork = FALSE;
876 checked_free(directory);
878 if (!gfx.override_level_music)
880 /* 1st try: look for special artwork in current level series directory */
881 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
882 if (fileExists(directory))
887 /* check if there is special artwork configured in level series config */
888 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
890 /* 2nd try: look for special artwork configured in level series config */
891 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
892 if (fileExists(directory))
897 /* take missing artwork configured in level set config from default */
898 skip_setup_artwork = TRUE;
902 if (!skip_setup_artwork)
904 /* 3rd try: look for special artwork in configured artwork directory */
905 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
906 if (fileExists(directory))
912 /* 4th try: look for default artwork in new default artwork directory */
913 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
914 if (fileExists(directory))
919 /* 5th try: look for default artwork in old default artwork directory */
920 directory = getStringCopy(options.music_directory);
921 if (fileExists(directory))
924 return NULL; /* cannot find specified artwork file anywhere */
927 void InitTapeDirectory(char *level_subdir)
929 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
930 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
931 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
934 void InitScoreDirectory(char *level_subdir)
936 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
937 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
938 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
941 static void SaveUserLevelInfo();
943 void InitUserLevelDirectory(char *level_subdir)
945 if (!fileExists(getUserLevelDir(level_subdir)))
947 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
948 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
949 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
955 void InitLevelSetupDirectory(char *level_subdir)
957 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
958 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
959 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
962 void InitCacheDirectory()
964 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
965 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
969 /* ------------------------------------------------------------------------- */
970 /* some functions to handle lists of level and artwork directories */
971 /* ------------------------------------------------------------------------- */
973 TreeInfo *newTreeInfo()
975 return checked_calloc(sizeof(TreeInfo));
978 TreeInfo *newTreeInfo_setDefaults(int type)
980 TreeInfo *ti = newTreeInfo();
982 setTreeInfoToDefaults(ti, type);
987 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
989 node_new->next = *node_first;
990 *node_first = node_new;
993 int numTreeInfo(TreeInfo *node)
1006 boolean validLevelSeries(TreeInfo *node)
1008 return (node != NULL && !node->node_group && !node->parent_link);
1011 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1016 if (node->node_group) /* enter level group (step down into tree) */
1017 return getFirstValidTreeInfoEntry(node->node_group);
1018 else if (node->parent_link) /* skip start entry of level group */
1020 if (node->next) /* get first real level series entry */
1021 return getFirstValidTreeInfoEntry(node->next);
1022 else /* leave empty level group and go on */
1023 return getFirstValidTreeInfoEntry(node->node_parent->next);
1025 else /* this seems to be a regular level series */
1029 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1034 if (node->node_parent == NULL) /* top level group */
1035 return *node->node_top;
1036 else /* sub level group */
1037 return node->node_parent->node_group;
1040 int numTreeInfoInGroup(TreeInfo *node)
1042 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1045 int posTreeInfo(TreeInfo *node)
1047 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1052 if (node_cmp == node)
1056 node_cmp = node_cmp->next;
1062 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1064 TreeInfo *node_default = node;
1076 return node_default;
1079 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1081 if (identifier == NULL)
1086 if (node->node_group)
1088 TreeInfo *node_group;
1090 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1095 else if (!node->parent_link)
1097 if (strEqual(identifier, node->identifier))
1107 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1108 TreeInfo *node, boolean skip_sets_without_levels)
1115 if (!node->parent_link && !node->level_group &&
1116 skip_sets_without_levels && node->levels == 0)
1117 return cloneTreeNode(node_top, node_parent, node->next,
1118 skip_sets_without_levels);
1121 node_new = getTreeInfoCopy(node); /* copy complete node */
1123 node_new = newTreeInfo();
1125 *node_new = *node; /* copy complete node */
1128 node_new->node_top = node_top; /* correct top node link */
1129 node_new->node_parent = node_parent; /* correct parent node link */
1131 if (node->level_group)
1132 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1133 skip_sets_without_levels);
1135 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1136 skip_sets_without_levels);
1141 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1143 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1145 *ti_new = ti_cloned;
1148 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1150 boolean settings_changed = FALSE;
1154 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1155 !strEqual(node->graphics_set, node->graphics_set_ecs))
1157 setString(&node->graphics_set, node->graphics_set_ecs);
1158 settings_changed = TRUE;
1160 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1161 !strEqual(node->graphics_set, node->graphics_set_aga))
1163 setString(&node->graphics_set, node->graphics_set_aga);
1164 settings_changed = TRUE;
1167 if (node->node_group != NULL)
1168 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1173 return settings_changed;
1176 void dumpTreeInfo(TreeInfo *node, int depth)
1180 printf("Dumping TreeInfo:\n");
1184 for (i = 0; i < (depth + 1) * 3; i++)
1187 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1188 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1190 if (node->node_group != NULL)
1191 dumpTreeInfo(node->node_group, depth + 1);
1197 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1198 int (*compare_function)(const void *,
1201 int num_nodes = numTreeInfo(*node_first);
1202 TreeInfo **sort_array;
1203 TreeInfo *node = *node_first;
1209 /* allocate array for sorting structure pointers */
1210 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1212 /* writing structure pointers to sorting array */
1213 while (i < num_nodes && node) /* double boundary check... */
1215 sort_array[i] = node;
1221 /* sorting the structure pointers in the sorting array */
1222 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1225 /* update the linkage of list elements with the sorted node array */
1226 for (i = 0; i < num_nodes - 1; i++)
1227 sort_array[i]->next = sort_array[i + 1];
1228 sort_array[num_nodes - 1]->next = NULL;
1230 /* update the linkage of the main list anchor pointer */
1231 *node_first = sort_array[0];
1235 /* now recursively sort the level group structures */
1239 if (node->node_group != NULL)
1240 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1246 void sortTreeInfo(TreeInfo **node_first)
1248 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1252 /* ========================================================================= */
1253 /* some stuff from "files.c" */
1254 /* ========================================================================= */
1256 #if defined(PLATFORM_WIN32)
1258 #define S_IRGRP S_IRUSR
1261 #define S_IROTH S_IRUSR
1264 #define S_IWGRP S_IWUSR
1267 #define S_IWOTH S_IWUSR
1270 #define S_IXGRP S_IXUSR
1273 #define S_IXOTH S_IXUSR
1276 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1281 #endif /* PLATFORM_WIN32 */
1283 /* file permissions for newly written files */
1284 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1285 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1286 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1288 #define MODE_W_PRIVATE (S_IWUSR)
1289 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1290 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1292 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1293 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1295 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1296 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1300 static char *dir = NULL;
1302 #if defined(PLATFORM_WIN32)
1305 dir = checked_malloc(MAX_PATH + 1);
1307 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1310 #elif defined(PLATFORM_UNIX)
1313 if ((dir = getenv("HOME")) == NULL)
1317 if ((pwd = getpwuid(getuid())) != NULL)
1318 dir = getStringCopy(pwd->pw_dir);
1330 char *getCommonDataDir(void)
1332 static char *common_data_dir = NULL;
1334 #if defined(PLATFORM_WIN32)
1335 if (common_data_dir == NULL)
1337 char *dir = checked_malloc(MAX_PATH + 1);
1339 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1340 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1341 common_data_dir = getPath2(dir, program.userdata_subdir);
1343 common_data_dir = options.rw_base_directory;
1346 if (common_data_dir == NULL)
1347 common_data_dir = options.rw_base_directory;
1350 return common_data_dir;
1353 char *getPersonalDataDir(void)
1355 static char *personal_data_dir = NULL;
1357 #if defined(PLATFORM_MACOSX)
1358 if (personal_data_dir == NULL)
1359 personal_data_dir = getPath2(getHomeDir(), "Documents");
1361 if (personal_data_dir == NULL)
1362 personal_data_dir = getHomeDir();
1365 return personal_data_dir;
1368 char *getUserGameDataDir(void)
1370 static char *user_game_data_dir = NULL;
1372 if (user_game_data_dir == NULL)
1373 user_game_data_dir = getPath2(getPersonalDataDir(),
1374 program.userdata_subdir);
1376 return user_game_data_dir;
1379 void updateUserGameDataDir()
1381 #if defined(PLATFORM_MACOSX)
1382 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1383 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1385 /* convert old Unix style game data directory to Mac OS X style, if needed */
1386 if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1388 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1390 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1391 userdata_dir_old, userdata_dir_new);
1393 /* continue using Unix style data directory -- this should not happen */
1394 program.userdata_path = getPath2(getPersonalDataDir(),
1395 program.userdata_subdir_unix);
1399 free(userdata_dir_old);
1405 return getUserGameDataDir();
1408 static mode_t posix_umask(mode_t mask)
1410 #if defined(PLATFORM_UNIX)
1417 static int posix_mkdir(const char *pathname, mode_t mode)
1419 #if defined(PLATFORM_WIN32)
1420 return mkdir(pathname);
1422 return mkdir(pathname, mode);
1426 static boolean posix_process_running_setgid()
1428 #if defined(PLATFORM_UNIX)
1429 return (getgid() != getegid());
1435 void createDirectory(char *dir, char *text, int permission_class)
1437 /* leave "other" permissions in umask untouched, but ensure group parts
1438 of USERDATA_DIR_MODE are not masked */
1439 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1440 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1441 mode_t last_umask = posix_umask(0);
1442 mode_t group_umask = ~(dir_mode & S_IRWXG);
1443 int running_setgid = posix_process_running_setgid();
1445 /* if we're setgid, protect files against "other" */
1446 /* else keep umask(0) to make the dir world-writable */
1449 posix_umask(last_umask & group_umask);
1451 dir_mode |= MODE_W_ALL;
1453 if (!fileExists(dir))
1454 if (posix_mkdir(dir, dir_mode) != 0)
1455 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1457 if (permission_class == PERMS_PUBLIC && !running_setgid)
1458 chmod(dir, dir_mode);
1460 posix_umask(last_umask); /* restore previous umask */
1463 void InitUserDataDirectory()
1465 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1468 void SetFilePermissions(char *filename, int permission_class)
1470 int running_setgid = posix_process_running_setgid();
1471 int perms = (permission_class == PERMS_PRIVATE ?
1472 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1474 if (permission_class == PERMS_PUBLIC && !running_setgid)
1475 perms |= MODE_W_ALL;
1477 chmod(filename, perms);
1480 char *getCookie(char *file_type)
1482 static char cookie[MAX_COOKIE_LEN + 1];
1484 if (strlen(program.cookie_prefix) + 1 +
1485 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1486 return "[COOKIE ERROR]"; /* should never happen */
1488 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1489 program.cookie_prefix, file_type,
1490 program.version_major, program.version_minor);
1495 int getFileVersionFromCookieString(const char *cookie)
1497 const char *ptr_cookie1, *ptr_cookie2;
1498 const char *pattern1 = "_FILE_VERSION_";
1499 const char *pattern2 = "?.?";
1500 const int len_cookie = strlen(cookie);
1501 const int len_pattern1 = strlen(pattern1);
1502 const int len_pattern2 = strlen(pattern2);
1503 const int len_pattern = len_pattern1 + len_pattern2;
1504 int version_major, version_minor;
1506 if (len_cookie <= len_pattern)
1509 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1510 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1512 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1515 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1516 ptr_cookie2[1] != '.' ||
1517 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1520 version_major = ptr_cookie2[0] - '0';
1521 version_minor = ptr_cookie2[2] - '0';
1523 return VERSION_IDENT(version_major, version_minor, 0, 0);
1526 boolean checkCookieString(const char *cookie, const char *template)
1528 const char *pattern = "_FILE_VERSION_?.?";
1529 const int len_cookie = strlen(cookie);
1530 const int len_template = strlen(template);
1531 const int len_pattern = strlen(pattern);
1533 if (len_cookie != len_template)
1536 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1542 /* ------------------------------------------------------------------------- */
1543 /* setup file list and hash handling functions */
1544 /* ------------------------------------------------------------------------- */
1546 char *getFormattedSetupEntry(char *token, char *value)
1549 static char entry[MAX_LINE_LEN];
1551 /* if value is an empty string, just return token without value */
1555 /* start with the token and some spaces to format output line */
1556 sprintf(entry, "%s:", token);
1557 for (i = strlen(entry); i < token_value_position; i++)
1560 /* continue with the token's value */
1561 strcat(entry, value);
1566 SetupFileList *newSetupFileList(char *token, char *value)
1568 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1570 new->token = getStringCopy(token);
1571 new->value = getStringCopy(value);
1578 void freeSetupFileList(SetupFileList *list)
1583 checked_free(list->token);
1584 checked_free(list->value);
1587 freeSetupFileList(list->next);
1592 char *getListEntry(SetupFileList *list, char *token)
1597 if (strEqual(list->token, token))
1600 return getListEntry(list->next, token);
1603 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1608 if (strEqual(list->token, token))
1610 checked_free(list->value);
1612 list->value = getStringCopy(value);
1616 else if (list->next == NULL)
1617 return (list->next = newSetupFileList(token, value));
1619 return setListEntry(list->next, token, value);
1622 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1627 if (list->next == NULL)
1628 return (list->next = newSetupFileList(token, value));
1630 return addListEntry(list->next, token, value);
1635 static void printSetupFileList(SetupFileList *list)
1640 printf("token: '%s'\n", list->token);
1641 printf("value: '%s'\n", list->value);
1643 printSetupFileList(list->next);
1649 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1650 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1651 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1652 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1654 #define insert_hash_entry hashtable_insert
1655 #define search_hash_entry hashtable_search
1656 #define change_hash_entry hashtable_change
1657 #define remove_hash_entry hashtable_remove
1660 unsigned int get_hash_from_key(void *key)
1665 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1666 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1667 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1668 it works better than many other constants, prime or not) has never been
1669 adequately explained.
1671 If you just want to have a good hash function, and cannot wait, djb2
1672 is one of the best string hash functions i know. It has excellent
1673 distribution and speed on many different sets of keys and table sizes.
1674 You are not likely to do better with one of the "well known" functions
1675 such as PJW, K&R, etc.
1677 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1680 char *str = (char *)key;
1681 unsigned int hash = 5381;
1684 while ((c = *str++))
1685 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1690 static int keys_are_equal(void *key1, void *key2)
1692 return (strEqual((char *)key1, (char *)key2));
1695 SetupFileHash *newSetupFileHash()
1697 SetupFileHash *new_hash =
1698 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1700 if (new_hash == NULL)
1701 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1706 void freeSetupFileHash(SetupFileHash *hash)
1711 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1714 char *getHashEntry(SetupFileHash *hash, char *token)
1719 return search_hash_entry(hash, token);
1722 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1729 value_copy = getStringCopy(value);
1731 /* change value; if it does not exist, insert it as new */
1732 if (!change_hash_entry(hash, token, value_copy))
1733 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1734 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1737 char *removeHashEntry(SetupFileHash *hash, char *token)
1742 return remove_hash_entry(hash, token);
1746 static void printSetupFileHash(SetupFileHash *hash)
1748 BEGIN_HASH_ITERATION(hash, itr)
1750 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1751 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1753 END_HASH_ITERATION(hash, itr)
1757 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1758 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1759 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1761 static boolean token_value_separator_found = FALSE;
1762 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1763 static boolean token_value_separator_warning = FALSE;
1765 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1766 static boolean token_already_exists_warning = FALSE;
1769 static boolean getTokenValueFromSetupLineExt(char *line,
1770 char **token_ptr, char **value_ptr,
1771 char *filename, char *line_raw,
1773 boolean separator_required)
1775 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1776 char *token, *value, *line_ptr;
1778 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1779 if (line_raw == NULL)
1781 strncpy(line_copy, line, MAX_LINE_LEN);
1782 line_copy[MAX_LINE_LEN] = '\0';
1785 strcpy(line_raw_copy, line_copy);
1786 line_raw = line_raw_copy;
1789 /* cut trailing comment from input line */
1790 for (line_ptr = line; *line_ptr; line_ptr++)
1792 if (*line_ptr == '#')
1799 /* cut trailing whitespaces from input line */
1800 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1801 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1804 /* ignore empty lines */
1808 /* cut leading whitespaces from token */
1809 for (token = line; *token; token++)
1810 if (*token != ' ' && *token != '\t')
1813 /* start with empty value as reliable default */
1816 token_value_separator_found = FALSE;
1818 /* find end of token to determine start of value */
1819 for (line_ptr = token; *line_ptr; line_ptr++)
1822 /* first look for an explicit token/value separator, like ':' or '=' */
1823 if (*line_ptr == ':' || *line_ptr == '=')
1825 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1828 *line_ptr = '\0'; /* terminate token string */
1829 value = line_ptr + 1; /* set beginning of value */
1831 token_value_separator_found = TRUE;
1837 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1838 /* fallback: if no token/value separator found, also allow whitespaces */
1839 if (!token_value_separator_found && !separator_required)
1841 for (line_ptr = token; *line_ptr; line_ptr++)
1843 if (*line_ptr == ' ' || *line_ptr == '\t')
1845 *line_ptr = '\0'; /* terminate token string */
1846 value = line_ptr + 1; /* set beginning of value */
1848 token_value_separator_found = TRUE;
1854 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1855 if (token_value_separator_found)
1857 if (!token_value_separator_warning)
1859 Error(ERR_INFO_LINE, "-");
1861 if (filename != NULL)
1863 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1864 Error(ERR_INFO, "- config file: '%s'", filename);
1868 Error(ERR_WARN, "missing token/value separator(s):");
1871 token_value_separator_warning = TRUE;
1874 if (filename != NULL)
1875 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1877 Error(ERR_INFO, "- line: '%s'", line_raw);
1883 /* cut trailing whitespaces from token */
1884 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1885 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1888 /* cut leading whitespaces from value */
1889 for (; *value; value++)
1890 if (*value != ' ' && *value != '\t')
1895 value = "true"; /* treat tokens without value as "true" */
1904 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1906 /* while the internal (old) interface does not require a token/value
1907 separator (for downwards compatibility with existing files which
1908 don't use them), it is mandatory for the external (new) interface */
1910 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1914 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1915 boolean top_recursion_level, boolean is_hash)
1917 static SetupFileHash *include_filename_hash = NULL;
1918 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1919 char *token, *value, *line_ptr;
1920 void *insert_ptr = NULL;
1921 boolean read_continued_line = FALSE;
1923 int line_nr = 0, token_count = 0, include_count = 0;
1925 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1926 token_value_separator_warning = FALSE;
1929 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1930 token_already_exists_warning = FALSE;
1933 if (!(file = fopen(filename, MODE_READ)))
1935 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1940 /* use "insert pointer" to store list end for constant insertion complexity */
1942 insert_ptr = setup_file_data;
1944 /* on top invocation, create hash to mark included files (to prevent loops) */
1945 if (top_recursion_level)
1946 include_filename_hash = newSetupFileHash();
1948 /* mark this file as already included (to prevent including it again) */
1949 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1953 /* read next line of input file */
1954 if (!fgets(line, MAX_LINE_LEN, file))
1957 /* check if line was completely read and is terminated by line break */
1958 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1961 /* cut trailing line break (this can be newline and/or carriage return) */
1962 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1963 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1966 /* copy raw input line for later use (mainly debugging output) */
1967 strcpy(line_raw, line);
1969 if (read_continued_line)
1972 /* !!! ??? WHY ??? !!! */
1973 /* cut leading whitespaces from input line */
1974 for (line_ptr = line; *line_ptr; line_ptr++)
1975 if (*line_ptr != ' ' && *line_ptr != '\t')
1979 /* append new line to existing line, if there is enough space */
1980 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1981 strcat(previous_line, line_ptr);
1983 strcpy(line, previous_line); /* copy storage buffer to line */
1985 read_continued_line = FALSE;
1988 /* if the last character is '\', continue at next line */
1989 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1991 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1992 strcpy(previous_line, line); /* copy line to storage buffer */
1994 read_continued_line = TRUE;
1999 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2000 line_raw, line_nr, FALSE))
2005 if (strEqual(token, "include"))
2007 if (getHashEntry(include_filename_hash, value) == NULL)
2009 char *basepath = getBasePath(filename);
2010 char *basename = getBaseName(value);
2011 char *filename_include = getPath2(basepath, basename);
2014 Error(ERR_INFO, "[including file '%s']", filename_include);
2017 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2021 free(filename_include);
2027 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2034 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2036 getHashEntry((SetupFileHash *)setup_file_data, token);
2038 if (old_value != NULL)
2040 if (!token_already_exists_warning)
2042 Error(ERR_INFO_LINE, "-");
2043 Error(ERR_WARN, "duplicate token(s) found in config file:");
2044 Error(ERR_INFO, "- config file: '%s'", filename);
2046 token_already_exists_warning = TRUE;
2049 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2050 Error(ERR_INFO, " old value: '%s'", old_value);
2051 Error(ERR_INFO, " new value: '%s'", value);
2055 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2059 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2069 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2070 if (token_value_separator_warning)
2071 Error(ERR_INFO_LINE, "-");
2074 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2075 if (token_already_exists_warning)
2076 Error(ERR_INFO_LINE, "-");
2079 if (token_count == 0 && include_count == 0)
2080 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2082 if (top_recursion_level)
2083 freeSetupFileHash(include_filename_hash);
2090 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2091 boolean top_recursion_level, boolean is_hash)
2093 static SetupFileHash *include_filename_hash = NULL;
2094 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2095 char *token, *value, *line_ptr;
2096 void *insert_ptr = NULL;
2097 boolean read_continued_line = FALSE;
2100 int token_count = 0;
2102 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2103 token_value_separator_warning = FALSE;
2106 if (!(file = fopen(filename, MODE_READ)))
2108 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2113 /* use "insert pointer" to store list end for constant insertion complexity */
2115 insert_ptr = setup_file_data;
2117 /* on top invocation, create hash to mark included files (to prevent loops) */
2118 if (top_recursion_level)
2119 include_filename_hash = newSetupFileHash();
2121 /* mark this file as already included (to prevent including it again) */
2122 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2126 /* read next line of input file */
2127 if (!fgets(line, MAX_LINE_LEN, file))
2130 /* check if line was completely read and is terminated by line break */
2131 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2134 /* cut trailing line break (this can be newline and/or carriage return) */
2135 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2136 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2139 /* copy raw input line for later use (mainly debugging output) */
2140 strcpy(line_raw, line);
2142 if (read_continued_line)
2144 /* cut leading whitespaces from input line */
2145 for (line_ptr = line; *line_ptr; line_ptr++)
2146 if (*line_ptr != ' ' && *line_ptr != '\t')
2149 /* append new line to existing line, if there is enough space */
2150 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2151 strcat(previous_line, line_ptr);
2153 strcpy(line, previous_line); /* copy storage buffer to line */
2155 read_continued_line = FALSE;
2158 /* if the last character is '\', continue at next line */
2159 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2161 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2162 strcpy(previous_line, line); /* copy line to storage buffer */
2164 read_continued_line = TRUE;
2169 /* cut trailing comment from input line */
2170 for (line_ptr = line; *line_ptr; line_ptr++)
2172 if (*line_ptr == '#')
2179 /* cut trailing whitespaces from input line */
2180 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2181 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2184 /* ignore empty lines */
2188 /* cut leading whitespaces from token */
2189 for (token = line; *token; token++)
2190 if (*token != ' ' && *token != '\t')
2193 /* start with empty value as reliable default */
2196 token_value_separator_found = FALSE;
2198 /* find end of token to determine start of value */
2199 for (line_ptr = token; *line_ptr; line_ptr++)
2202 /* first look for an explicit token/value separator, like ':' or '=' */
2203 if (*line_ptr == ':' || *line_ptr == '=')
2205 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2208 *line_ptr = '\0'; /* terminate token string */
2209 value = line_ptr + 1; /* set beginning of value */
2211 token_value_separator_found = TRUE;
2217 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2218 /* fallback: if no token/value separator found, also allow whitespaces */
2219 if (!token_value_separator_found)
2221 for (line_ptr = token; *line_ptr; line_ptr++)
2223 if (*line_ptr == ' ' || *line_ptr == '\t')
2225 *line_ptr = '\0'; /* terminate token string */
2226 value = line_ptr + 1; /* set beginning of value */
2228 token_value_separator_found = TRUE;
2234 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2235 if (token_value_separator_found)
2237 if (!token_value_separator_warning)
2239 Error(ERR_INFO_LINE, "-");
2240 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2241 Error(ERR_INFO, "- config file: '%s'", filename);
2243 token_value_separator_warning = TRUE;
2246 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2252 /* cut trailing whitespaces from token */
2253 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2254 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2257 /* cut leading whitespaces from value */
2258 for (; *value; value++)
2259 if (*value != ' ' && *value != '\t')
2264 value = "true"; /* treat tokens without value as "true" */
2269 if (strEqual(token, "include"))
2271 if (getHashEntry(include_filename_hash, value) == NULL)
2273 char *basepath = getBasePath(filename);
2274 char *basename = getBaseName(value);
2275 char *filename_include = getPath2(basepath, basename);
2278 Error(ERR_INFO, "[including file '%s']", filename_include);
2281 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2285 free(filename_include);
2289 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2295 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2297 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2306 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2307 if (token_value_separator_warning)
2308 Error(ERR_INFO_LINE, "-");
2311 if (token_count == 0)
2312 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2314 if (top_recursion_level)
2315 freeSetupFileHash(include_filename_hash);
2321 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2325 if (!(file = fopen(filename, MODE_WRITE)))
2327 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2332 BEGIN_HASH_ITERATION(hash, itr)
2334 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2335 HASH_ITERATION_VALUE(itr)));
2337 END_HASH_ITERATION(hash, itr)
2342 SetupFileList *loadSetupFileList(char *filename)
2344 SetupFileList *setup_file_list = newSetupFileList("", "");
2345 SetupFileList *first_valid_list_entry;
2347 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2349 freeSetupFileList(setup_file_list);
2354 first_valid_list_entry = setup_file_list->next;
2356 /* free empty list header */
2357 setup_file_list->next = NULL;
2358 freeSetupFileList(setup_file_list);
2360 return first_valid_list_entry;
2363 SetupFileHash *loadSetupFileHash(char *filename)
2365 SetupFileHash *setup_file_hash = newSetupFileHash();
2367 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2369 freeSetupFileHash(setup_file_hash);
2374 return setup_file_hash;
2377 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2378 char *filename, char *identifier)
2380 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2383 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2384 else if (!checkCookieString(value, identifier))
2385 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2389 /* ========================================================================= */
2390 /* setup file stuff */
2391 /* ========================================================================= */
2393 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2394 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2395 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2397 /* level directory info */
2398 #define LEVELINFO_TOKEN_IDENTIFIER 0
2399 #define LEVELINFO_TOKEN_NAME 1
2400 #define LEVELINFO_TOKEN_NAME_SORTING 2
2401 #define LEVELINFO_TOKEN_AUTHOR 3
2402 #define LEVELINFO_TOKEN_YEAR 4
2403 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2404 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2405 #define LEVELINFO_TOKEN_TESTED_BY 7
2406 #define LEVELINFO_TOKEN_LEVELS 8
2407 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2408 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2409 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2410 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2411 #define LEVELINFO_TOKEN_READONLY 13
2412 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2413 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2414 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2415 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2416 #define LEVELINFO_TOKEN_MUSIC_SET 18
2417 #define LEVELINFO_TOKEN_FILENAME 19
2418 #define LEVELINFO_TOKEN_FILETYPE 20
2419 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2420 #define LEVELINFO_TOKEN_HANDICAP 22
2421 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2423 #define NUM_LEVELINFO_TOKENS 24
2425 static LevelDirTree ldi;
2427 static struct TokenInfo levelinfo_tokens[] =
2429 /* level directory info */
2430 { TYPE_STRING, &ldi.identifier, "identifier" },
2431 { TYPE_STRING, &ldi.name, "name" },
2432 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2433 { TYPE_STRING, &ldi.author, "author" },
2434 { TYPE_STRING, &ldi.year, "year" },
2435 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2436 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2437 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2438 { TYPE_INTEGER, &ldi.levels, "levels" },
2439 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2440 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2441 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2442 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2443 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2444 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2445 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2446 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2447 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2448 { TYPE_STRING, &ldi.music_set, "music_set" },
2449 { TYPE_STRING, &ldi.level_filename, "filename" },
2450 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2451 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2452 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2453 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2456 static struct TokenInfo artworkinfo_tokens[] =
2458 /* artwork directory info */
2459 { TYPE_STRING, &ldi.identifier, "identifier" },
2460 { TYPE_STRING, &ldi.subdir, "subdir" },
2461 { TYPE_STRING, &ldi.name, "name" },
2462 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2463 { TYPE_STRING, &ldi.author, "author" },
2464 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2465 { TYPE_STRING, &ldi.basepath, "basepath" },
2466 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2467 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2468 { TYPE_INTEGER, &ldi.color, "color" },
2469 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2474 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2478 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2479 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2480 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2481 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2484 ti->node_parent = NULL;
2485 ti->node_group = NULL;
2492 ti->fullpath = NULL;
2493 ti->basepath = NULL;
2494 ti->identifier = NULL;
2495 ti->name = getStringCopy(ANONYMOUS_NAME);
2496 ti->name_sorting = NULL;
2497 ti->author = getStringCopy(ANONYMOUS_NAME);
2500 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2501 ti->latest_engine = FALSE; /* default: get from level */
2502 ti->parent_link = FALSE;
2503 ti->in_user_dir = FALSE;
2504 ti->user_defined = FALSE;
2506 ti->class_desc = NULL;
2508 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2510 if (ti->type == TREE_TYPE_LEVEL_DIR)
2512 ti->imported_from = NULL;
2513 ti->imported_by = NULL;
2514 ti->tested_by = NULL;
2516 ti->graphics_set_ecs = NULL;
2517 ti->graphics_set_aga = NULL;
2518 ti->graphics_set = NULL;
2519 ti->sounds_set = NULL;
2520 ti->music_set = NULL;
2521 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2522 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2523 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2525 ti->level_filename = NULL;
2526 ti->level_filetype = NULL;
2528 ti->special_flags = NULL;
2531 ti->first_level = 0;
2533 ti->level_group = FALSE;
2534 ti->handicap_level = 0;
2535 ti->readonly = TRUE;
2536 ti->handicap = TRUE;
2537 ti->skip_levels = FALSE;
2541 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2545 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2547 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2552 /* copy all values from the parent structure */
2554 ti->type = parent->type;
2556 ti->node_top = parent->node_top;
2557 ti->node_parent = parent;
2558 ti->node_group = NULL;
2565 ti->fullpath = NULL;
2566 ti->basepath = NULL;
2567 ti->identifier = NULL;
2568 ti->name = getStringCopy(ANONYMOUS_NAME);
2569 ti->name_sorting = NULL;
2570 ti->author = getStringCopy(parent->author);
2571 ti->year = getStringCopy(parent->year);
2573 ti->sort_priority = parent->sort_priority;
2574 ti->latest_engine = parent->latest_engine;
2575 ti->parent_link = FALSE;
2576 ti->in_user_dir = parent->in_user_dir;
2577 ti->user_defined = parent->user_defined;
2578 ti->color = parent->color;
2579 ti->class_desc = getStringCopy(parent->class_desc);
2581 ti->infotext = getStringCopy(parent->infotext);
2583 if (ti->type == TREE_TYPE_LEVEL_DIR)
2585 ti->imported_from = getStringCopy(parent->imported_from);
2586 ti->imported_by = getStringCopy(parent->imported_by);
2587 ti->tested_by = getStringCopy(parent->tested_by);
2589 ti->graphics_set_ecs = NULL;
2590 ti->graphics_set_aga = NULL;
2591 ti->graphics_set = NULL;
2592 ti->sounds_set = NULL;
2593 ti->music_set = NULL;
2594 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2595 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2596 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2598 ti->level_filename = NULL;
2599 ti->level_filetype = NULL;
2601 ti->special_flags = getStringCopy(parent->special_flags);
2604 ti->first_level = 0;
2606 ti->level_group = FALSE;
2607 ti->handicap_level = 0;
2609 ti->readonly = parent->readonly;
2611 ti->readonly = TRUE;
2613 ti->handicap = TRUE;
2614 ti->skip_levels = FALSE;
2618 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2620 TreeInfo *ti_copy = newTreeInfo();
2622 /* copy all values from the original structure */
2624 ti_copy->type = ti->type;
2626 ti_copy->node_top = ti->node_top;
2627 ti_copy->node_parent = ti->node_parent;
2628 ti_copy->node_group = ti->node_group;
2629 ti_copy->next = ti->next;
2631 ti_copy->cl_first = ti->cl_first;
2632 ti_copy->cl_cursor = ti->cl_cursor;
2634 ti_copy->subdir = getStringCopy(ti->subdir);
2635 ti_copy->fullpath = getStringCopy(ti->fullpath);
2636 ti_copy->basepath = getStringCopy(ti->basepath);
2637 ti_copy->identifier = getStringCopy(ti->identifier);
2638 ti_copy->name = getStringCopy(ti->name);
2639 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2640 ti_copy->author = getStringCopy(ti->author);
2641 ti_copy->year = getStringCopy(ti->year);
2642 ti_copy->imported_from = getStringCopy(ti->imported_from);
2643 ti_copy->imported_by = getStringCopy(ti->imported_by);
2644 ti_copy->tested_by = getStringCopy(ti->tested_by);
2646 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2647 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2648 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2649 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2650 ti_copy->music_set = getStringCopy(ti->music_set);
2651 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2652 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2653 ti_copy->music_path = getStringCopy(ti->music_path);
2655 ti_copy->level_filename = getStringCopy(ti->level_filename);
2656 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2658 ti_copy->special_flags = getStringCopy(ti->special_flags);
2660 ti_copy->levels = ti->levels;
2661 ti_copy->first_level = ti->first_level;
2662 ti_copy->last_level = ti->last_level;
2663 ti_copy->sort_priority = ti->sort_priority;
2665 ti_copy->latest_engine = ti->latest_engine;
2667 ti_copy->level_group = ti->level_group;
2668 ti_copy->parent_link = ti->parent_link;
2669 ti_copy->in_user_dir = ti->in_user_dir;
2670 ti_copy->user_defined = ti->user_defined;
2671 ti_copy->readonly = ti->readonly;
2672 ti_copy->handicap = ti->handicap;
2673 ti_copy->skip_levels = ti->skip_levels;
2675 ti_copy->color = ti->color;
2676 ti_copy->class_desc = getStringCopy(ti->class_desc);
2677 ti_copy->handicap_level = ti->handicap_level;
2679 ti_copy->infotext = getStringCopy(ti->infotext);
2684 void freeTreeInfo(TreeInfo *ti)
2689 checked_free(ti->subdir);
2690 checked_free(ti->fullpath);
2691 checked_free(ti->basepath);
2692 checked_free(ti->identifier);
2694 checked_free(ti->name);
2695 checked_free(ti->name_sorting);
2696 checked_free(ti->author);
2697 checked_free(ti->year);
2699 checked_free(ti->class_desc);
2701 checked_free(ti->infotext);
2703 if (ti->type == TREE_TYPE_LEVEL_DIR)
2705 checked_free(ti->imported_from);
2706 checked_free(ti->imported_by);
2707 checked_free(ti->tested_by);
2709 checked_free(ti->graphics_set_ecs);
2710 checked_free(ti->graphics_set_aga);
2711 checked_free(ti->graphics_set);
2712 checked_free(ti->sounds_set);
2713 checked_free(ti->music_set);
2715 checked_free(ti->graphics_path);
2716 checked_free(ti->sounds_path);
2717 checked_free(ti->music_path);
2719 checked_free(ti->level_filename);
2720 checked_free(ti->level_filetype);
2722 checked_free(ti->special_flags);
2728 void setSetupInfo(struct TokenInfo *token_info,
2729 int token_nr, char *token_value)
2731 int token_type = token_info[token_nr].type;
2732 void *setup_value = token_info[token_nr].value;
2734 if (token_value == NULL)
2737 /* set setup field to corresponding token value */
2742 *(boolean *)setup_value = get_boolean_from_string(token_value);
2746 *(int *)setup_value = get_switch3_from_string(token_value);
2750 *(Key *)setup_value = getKeyFromKeyName(token_value);
2754 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2758 *(int *)setup_value = get_integer_from_string(token_value);
2762 checked_free(*(char **)setup_value);
2763 *(char **)setup_value = getStringCopy(token_value);
2771 static int compareTreeInfoEntries(const void *object1, const void *object2)
2773 const TreeInfo *entry1 = *((TreeInfo **)object1);
2774 const TreeInfo *entry2 = *((TreeInfo **)object2);
2775 int class_sorting1, class_sorting2;
2778 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2780 class_sorting1 = LEVELSORTING(entry1);
2781 class_sorting2 = LEVELSORTING(entry2);
2785 class_sorting1 = ARTWORKSORTING(entry1);
2786 class_sorting2 = ARTWORKSORTING(entry2);
2789 if (entry1->parent_link || entry2->parent_link)
2790 compare_result = (entry1->parent_link ? -1 : +1);
2791 else if (entry1->sort_priority == entry2->sort_priority)
2793 char *name1 = getStringToLower(entry1->name_sorting);
2794 char *name2 = getStringToLower(entry2->name_sorting);
2796 compare_result = strcmp(name1, name2);
2801 else if (class_sorting1 == class_sorting2)
2802 compare_result = entry1->sort_priority - entry2->sort_priority;
2804 compare_result = class_sorting1 - class_sorting2;
2806 return compare_result;
2809 static void createParentTreeInfoNode(TreeInfo *node_parent)
2813 if (node_parent == NULL)
2816 ti_new = newTreeInfo();
2817 setTreeInfoToDefaults(ti_new, node_parent->type);
2819 ti_new->node_parent = node_parent;
2820 ti_new->parent_link = TRUE;
2822 setString(&ti_new->identifier, node_parent->identifier);
2823 setString(&ti_new->name, ".. (parent directory)");
2824 setString(&ti_new->name_sorting, ti_new->name);
2826 setString(&ti_new->subdir, "..");
2827 setString(&ti_new->fullpath, node_parent->fullpath);
2829 ti_new->sort_priority = node_parent->sort_priority;
2830 ti_new->latest_engine = node_parent->latest_engine;
2832 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2834 pushTreeInfo(&node_parent->node_group, ti_new);
2838 /* -------------------------------------------------------------------------- */
2839 /* functions for handling level and custom artwork info cache */
2840 /* -------------------------------------------------------------------------- */
2842 static void LoadArtworkInfoCache()
2844 InitCacheDirectory();
2846 if (artworkinfo_cache_old == NULL)
2848 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2850 /* try to load artwork info hash from already existing cache file */
2851 artworkinfo_cache_old = loadSetupFileHash(filename);
2853 /* if no artwork info cache file was found, start with empty hash */
2854 if (artworkinfo_cache_old == NULL)
2855 artworkinfo_cache_old = newSetupFileHash();
2860 if (artworkinfo_cache_new == NULL)
2861 artworkinfo_cache_new = newSetupFileHash();
2864 static void SaveArtworkInfoCache()
2866 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2868 InitCacheDirectory();
2870 saveSetupFileHash(artworkinfo_cache_new, filename);
2875 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2877 static char *prefix = NULL;
2879 checked_free(prefix);
2881 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2886 /* (identical to above function, but separate string buffer needed -- nasty) */
2887 static char *getCacheToken(char *prefix, char *suffix)
2889 static char *token = NULL;
2891 checked_free(token);
2893 token = getStringCat2WithSeparator(prefix, suffix, ".");
2898 static char *getFileTimestampString(char *filename)
2901 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2903 struct stat file_status;
2905 if (stat(filename, &file_status) != 0) /* cannot stat file */
2906 return getStringCopy(i_to_a(0));
2908 return getStringCopy(i_to_a(file_status.st_mtime));
2912 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2914 struct stat file_status;
2916 if (timestamp_string == NULL)
2919 if (stat(filename, &file_status) != 0) /* cannot stat file */
2922 return (file_status.st_mtime != atoi(timestamp_string));
2925 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2927 char *identifier = level_node->subdir;
2928 char *type_string = ARTWORK_DIRECTORY(type);
2929 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2930 char *token_main = getCacheToken(token_prefix, "CACHED");
2931 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2932 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2933 TreeInfo *artwork_info = NULL;
2935 if (!use_artworkinfo_cache)
2942 artwork_info = newTreeInfo();
2943 setTreeInfoToDefaults(artwork_info, type);
2945 /* set all structure fields according to the token/value pairs */
2946 ldi = *artwork_info;
2947 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2949 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2950 char *value = getHashEntry(artworkinfo_cache_old, token);
2952 setSetupInfo(artworkinfo_tokens, i, value);
2954 /* check if cache entry for this item is invalid or incomplete */
2958 Error(ERR_WARN, "cache entry '%s' invalid", token);
2965 *artwork_info = ldi;
2970 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2971 LEVELINFO_FILENAME);
2972 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2973 ARTWORKINFO_FILENAME(type));
2975 /* check if corresponding "levelinfo.conf" file has changed */
2976 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2977 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2979 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2982 /* check if corresponding "<artworkinfo>.conf" file has changed */
2983 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2984 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2986 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2991 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2994 checked_free(filename_levelinfo);
2995 checked_free(filename_artworkinfo);
2998 if (!cached && artwork_info != NULL)
3000 freeTreeInfo(artwork_info);
3005 return artwork_info;
3008 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3009 LevelDirTree *level_node, int type)
3011 char *identifier = level_node->subdir;
3012 char *type_string = ARTWORK_DIRECTORY(type);
3013 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3014 char *token_main = getCacheToken(token_prefix, "CACHED");
3015 boolean set_cache_timestamps = TRUE;
3018 setHashEntry(artworkinfo_cache_new, token_main, "true");
3020 if (set_cache_timestamps)
3022 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3023 LEVELINFO_FILENAME);
3024 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3025 ARTWORKINFO_FILENAME(type));
3026 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3027 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3029 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3030 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3032 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3033 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3035 checked_free(filename_levelinfo);
3036 checked_free(filename_artworkinfo);
3037 checked_free(timestamp_levelinfo);
3038 checked_free(timestamp_artworkinfo);
3041 ldi = *artwork_info;
3042 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3044 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3045 char *value = getSetupValue(artworkinfo_tokens[i].type,
3046 artworkinfo_tokens[i].value);
3048 setHashEntry(artworkinfo_cache_new, token, value);
3053 /* -------------------------------------------------------------------------- */
3054 /* functions for loading level info and custom artwork info */
3055 /* -------------------------------------------------------------------------- */
3057 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3058 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3060 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3061 TreeInfo *node_parent,
3062 char *level_directory,
3063 char *directory_name)
3066 static unsigned int progress_delay = 0;
3067 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3069 char *directory_path = getPath2(level_directory, directory_name);
3070 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3071 SetupFileHash *setup_file_hash;
3072 LevelDirTree *leveldir_new = NULL;
3075 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3076 if (!options.debug && !fileExists(filename))
3078 free(directory_path);
3084 setup_file_hash = loadSetupFileHash(filename);
3086 if (setup_file_hash == NULL)
3088 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3090 free(directory_path);
3096 leveldir_new = newTreeInfo();
3099 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3101 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3103 leveldir_new->subdir = getStringCopy(directory_name);
3105 checkSetupFileHashIdentifier(setup_file_hash, filename,
3106 getCookie("LEVELINFO"));
3108 /* set all structure fields according to the token/value pairs */
3109 ldi = *leveldir_new;
3110 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3111 setSetupInfo(levelinfo_tokens, i,
3112 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3113 *leveldir_new = ldi;
3115 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3116 setString(&leveldir_new->name, leveldir_new->subdir);
3118 if (leveldir_new->identifier == NULL)
3119 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3121 if (leveldir_new->name_sorting == NULL)
3122 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3124 if (node_parent == NULL) /* top level group */
3126 leveldir_new->basepath = getStringCopy(level_directory);
3127 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3129 else /* sub level group */
3131 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3132 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3136 if (leveldir_new->levels < 1)
3137 leveldir_new->levels = 1;
3140 leveldir_new->last_level =
3141 leveldir_new->first_level + leveldir_new->levels - 1;
3143 leveldir_new->in_user_dir =
3144 (!strEqual(leveldir_new->basepath, options.level_directory));
3147 printf("::: '%s' -> %d\n",
3148 leveldir_new->identifier,
3149 leveldir_new->in_user_dir);
3152 /* adjust some settings if user's private level directory was detected */
3153 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3154 leveldir_new->in_user_dir &&
3155 (strEqual(leveldir_new->subdir, getLoginName()) ||
3156 strEqual(leveldir_new->name, getLoginName()) ||
3157 strEqual(leveldir_new->author, getRealName())))
3159 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3160 leveldir_new->readonly = FALSE;
3163 leveldir_new->user_defined =
3164 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3166 leveldir_new->color = LEVELCOLOR(leveldir_new);
3168 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3170 leveldir_new->handicap_level = /* set handicap to default value */
3171 (leveldir_new->user_defined || !leveldir_new->handicap ?
3172 leveldir_new->last_level : leveldir_new->first_level);
3176 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3177 leveldir_new->level_group);
3179 if (leveldir_new->level_group ||
3180 DelayReached(&progress_delay, progress_delay_value))
3181 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3184 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3188 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3190 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3192 /* skip level sets without levels (which are probably artwork base sets) */
3194 freeSetupFileHash(setup_file_hash);
3195 free(directory_path);
3203 pushTreeInfo(node_first, leveldir_new);
3205 freeSetupFileHash(setup_file_hash);
3207 if (leveldir_new->level_group)
3209 /* create node to link back to current level directory */
3210 createParentTreeInfoNode(leveldir_new);
3212 /* recursively step into sub-directory and look for more level series */
3213 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3214 leveldir_new, directory_path);
3217 free(directory_path);
3223 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3224 TreeInfo *node_parent,
3225 char *level_directory)
3228 struct dirent *dir_entry;
3229 boolean valid_entry_found = FALSE;
3231 if ((dir = opendir(level_directory)) == NULL)
3233 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3237 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3239 struct stat file_status;
3240 char *directory_name = dir_entry->d_name;
3241 char *directory_path = getPath2(level_directory, directory_name);
3243 /* skip entries for current and parent directory */
3244 if (strEqual(directory_name, ".") ||
3245 strEqual(directory_name, ".."))
3247 free(directory_path);
3251 /* find out if directory entry is itself a directory */
3252 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3253 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3255 free(directory_path);
3259 free(directory_path);
3261 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3262 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3263 strEqual(directory_name, MUSIC_DIRECTORY))
3266 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3273 /* special case: top level directory may directly contain "levelinfo.conf" */
3274 if (node_parent == NULL && !valid_entry_found)
3276 /* check if this directory directly contains a file "levelinfo.conf" */
3277 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3278 level_directory, ".");
3281 if (!valid_entry_found)
3282 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3286 boolean AdjustGraphicsForEMC()
3288 boolean settings_changed = FALSE;
3290 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3291 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3293 return settings_changed;
3296 void LoadLevelInfo()
3298 InitUserLevelDirectory(getLoginName());
3300 DrawInitText("Loading level series", 120, FC_GREEN);
3302 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3303 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3305 /* after loading all level set information, clone the level directory tree
3306 and remove all level sets without levels (these may still contain artwork
3307 to be offered in the setup menu as "custom artwork", and are therefore
3308 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3309 leveldir_first_all = leveldir_first;
3310 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3312 AdjustGraphicsForEMC();
3314 /* before sorting, the first entries will be from the user directory */
3315 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3317 if (leveldir_first == NULL)
3318 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3320 sortTreeInfo(&leveldir_first);
3323 dumpTreeInfo(leveldir_first, 0);
3327 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3328 TreeInfo *node_parent,
3329 char *base_directory,
3330 char *directory_name, int type)
3332 char *directory_path = getPath2(base_directory, directory_name);
3333 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3334 SetupFileHash *setup_file_hash = NULL;
3335 TreeInfo *artwork_new = NULL;
3338 if (fileExists(filename))
3339 setup_file_hash = loadSetupFileHash(filename);
3341 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3344 struct dirent *dir_entry;
3345 boolean valid_file_found = FALSE;
3347 if ((dir = opendir(directory_path)) != NULL)
3349 while ((dir_entry = readdir(dir)) != NULL)
3351 char *entry_name = dir_entry->d_name;
3353 if (FileIsArtworkType(entry_name, type))
3355 valid_file_found = TRUE;
3363 if (!valid_file_found)
3365 if (!strEqual(directory_name, "."))
3366 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3368 free(directory_path);
3375 artwork_new = newTreeInfo();
3378 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3380 setTreeInfoToDefaults(artwork_new, type);
3382 artwork_new->subdir = getStringCopy(directory_name);
3384 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3387 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3390 /* set all structure fields according to the token/value pairs */
3392 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3393 setSetupInfo(levelinfo_tokens, i,
3394 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3397 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3398 setString(&artwork_new->name, artwork_new->subdir);
3400 if (artwork_new->identifier == NULL)
3401 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3403 if (artwork_new->name_sorting == NULL)
3404 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3407 if (node_parent == NULL) /* top level group */
3409 artwork_new->basepath = getStringCopy(base_directory);
3410 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3412 else /* sub level group */
3414 artwork_new->basepath = getStringCopy(node_parent->basepath);
3415 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3418 artwork_new->in_user_dir =
3419 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3421 /* (may use ".sort_priority" from "setup_file_hash" above) */
3422 artwork_new->color = ARTWORKCOLOR(artwork_new);
3424 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3426 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3428 if (strEqual(artwork_new->subdir, "."))
3430 if (artwork_new->user_defined)
3432 setString(&artwork_new->identifier, "private");
3433 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3437 setString(&artwork_new->identifier, "classic");
3438 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3441 /* set to new values after changing ".sort_priority" */
3442 artwork_new->color = ARTWORKCOLOR(artwork_new);
3444 setString(&artwork_new->class_desc,
3445 getLevelClassDescription(artwork_new));
3449 setString(&artwork_new->identifier, artwork_new->subdir);
3452 setString(&artwork_new->name, artwork_new->identifier);
3453 setString(&artwork_new->name_sorting, artwork_new->name);
3457 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3460 pushTreeInfo(node_first, artwork_new);
3462 freeSetupFileHash(setup_file_hash);
3464 free(directory_path);
3470 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3471 TreeInfo *node_parent,
3472 char *base_directory, int type)
3475 struct dirent *dir_entry;
3476 boolean valid_entry_found = FALSE;
3478 if ((dir = opendir(base_directory)) == NULL)
3480 /* display error if directory is main "options.graphics_directory" etc. */
3481 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3482 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3487 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3489 struct stat file_status;
3490 char *directory_name = dir_entry->d_name;
3491 char *directory_path = getPath2(base_directory, directory_name);
3493 /* skip directory entries for current and parent directory */
3494 if (strEqual(directory_name, ".") ||
3495 strEqual(directory_name, ".."))
3497 free(directory_path);
3501 /* skip directory entries which are not a directory or are not accessible */
3502 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3503 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3505 free(directory_path);
3509 free(directory_path);
3511 /* check if this directory contains artwork with or without config file */
3512 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3514 directory_name, type);
3519 /* check if this directory directly contains artwork itself */
3520 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3521 base_directory, ".",
3523 if (!valid_entry_found)
3524 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3528 static TreeInfo *getDummyArtworkInfo(int type)
3530 /* this is only needed when there is completely no artwork available */
3531 TreeInfo *artwork_new = newTreeInfo();
3533 setTreeInfoToDefaults(artwork_new, type);
3535 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3536 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3537 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3539 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3540 setString(&artwork_new->name, UNDEFINED_FILENAME);
3541 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3546 void LoadArtworkInfo()
3548 LoadArtworkInfoCache();
3550 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3552 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3553 options.graphics_directory,
3554 TREE_TYPE_GRAPHICS_DIR);
3555 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3556 getUserGraphicsDir(),
3557 TREE_TYPE_GRAPHICS_DIR);
3559 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3560 options.sounds_directory,
3561 TREE_TYPE_SOUNDS_DIR);
3562 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3564 TREE_TYPE_SOUNDS_DIR);
3566 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3567 options.music_directory,
3568 TREE_TYPE_MUSIC_DIR);
3569 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3571 TREE_TYPE_MUSIC_DIR);
3573 if (artwork.gfx_first == NULL)
3574 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3575 if (artwork.snd_first == NULL)
3576 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3577 if (artwork.mus_first == NULL)
3578 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3580 /* before sorting, the first entries will be from the user directory */
3581 artwork.gfx_current =
3582 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3583 if (artwork.gfx_current == NULL)
3584 artwork.gfx_current =
3585 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3586 if (artwork.gfx_current == NULL)
3587 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3589 artwork.snd_current =
3590 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3591 if (artwork.snd_current == NULL)
3592 artwork.snd_current =
3593 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3594 if (artwork.snd_current == NULL)
3595 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3597 artwork.mus_current =
3598 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3599 if (artwork.mus_current == NULL)
3600 artwork.mus_current =
3601 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3602 if (artwork.mus_current == NULL)
3603 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3605 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3606 artwork.snd_current_identifier = artwork.snd_current->identifier;
3607 artwork.mus_current_identifier = artwork.mus_current->identifier;
3610 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3611 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3612 printf("music set == %s\n\n", artwork.mus_current_identifier);
3615 sortTreeInfo(&artwork.gfx_first);
3616 sortTreeInfo(&artwork.snd_first);
3617 sortTreeInfo(&artwork.mus_first);
3620 dumpTreeInfo(artwork.gfx_first, 0);
3621 dumpTreeInfo(artwork.snd_first, 0);
3622 dumpTreeInfo(artwork.mus_first, 0);
3626 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3627 LevelDirTree *level_node)
3630 static unsigned int progress_delay = 0;
3631 unsigned int progress_delay_value = 100; /* (in milliseconds) */
3633 int type = (*artwork_node)->type;
3635 /* recursively check all level directories for artwork sub-directories */
3639 /* check all tree entries for artwork, but skip parent link entries */
3640 if (!level_node->parent_link)
3642 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3643 boolean cached = (artwork_new != NULL);
3647 pushTreeInfo(artwork_node, artwork_new);
3651 TreeInfo *topnode_last = *artwork_node;
3652 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3653 ARTWORK_DIRECTORY(type));
3655 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3657 if (topnode_last != *artwork_node) /* check for newly added node */
3659 artwork_new = *artwork_node;
3661 setString(&artwork_new->identifier, level_node->subdir);
3662 setString(&artwork_new->name, level_node->name);
3663 setString(&artwork_new->name_sorting, level_node->name_sorting);
3665 artwork_new->sort_priority = level_node->sort_priority;
3666 artwork_new->color = LEVELCOLOR(artwork_new);
3672 /* insert artwork info (from old cache or filesystem) into new cache */
3673 if (artwork_new != NULL)
3674 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3678 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3679 level_node->level_group);
3681 if (level_node->level_group ||
3682 DelayReached(&progress_delay, progress_delay_value))
3683 DrawInitText(level_node->name, 150, FC_YELLOW);
3686 if (level_node->node_group != NULL)
3687 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3689 level_node = level_node->next;
3693 void LoadLevelArtworkInfo()
3695 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3697 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3698 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3699 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3701 SaveArtworkInfoCache();
3703 /* needed for reloading level artwork not known at ealier stage */
3705 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3707 artwork.gfx_current =
3708 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3709 if (artwork.gfx_current == NULL)
3710 artwork.gfx_current =
3711 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3712 if (artwork.gfx_current == NULL)
3713 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3716 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3718 artwork.snd_current =
3719 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3720 if (artwork.snd_current == NULL)
3721 artwork.snd_current =
3722 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3723 if (artwork.snd_current == NULL)
3724 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3727 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3729 artwork.mus_current =
3730 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3731 if (artwork.mus_current == NULL)
3732 artwork.mus_current =
3733 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3734 if (artwork.mus_current == NULL)
3735 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3738 sortTreeInfo(&artwork.gfx_first);
3739 sortTreeInfo(&artwork.snd_first);
3740 sortTreeInfo(&artwork.mus_first);
3743 dumpTreeInfo(artwork.gfx_first, 0);
3744 dumpTreeInfo(artwork.snd_first, 0);
3745 dumpTreeInfo(artwork.mus_first, 0);
3749 static void SaveUserLevelInfo()
3751 LevelDirTree *level_info;
3756 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3758 if (!(file = fopen(filename, MODE_WRITE)))
3760 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3765 level_info = newTreeInfo();
3767 /* always start with reliable default values */
3768 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3770 setString(&level_info->name, getLoginName());
3771 setString(&level_info->author, getRealName());
3772 level_info->levels = 100;
3773 level_info->first_level = 1;
3775 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3777 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3778 getCookie("LEVELINFO")));
3781 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3783 if (i == LEVELINFO_TOKEN_NAME ||
3784 i == LEVELINFO_TOKEN_AUTHOR ||
3785 i == LEVELINFO_TOKEN_LEVELS ||
3786 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3787 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3789 /* just to make things nicer :) */
3790 if (i == LEVELINFO_TOKEN_AUTHOR)
3791 fprintf(file, "\n");
3794 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3798 SetFilePermissions(filename, PERMS_PRIVATE);
3800 freeTreeInfo(level_info);
3804 char *getSetupValue(int type, void *value)
3806 static char value_string[MAX_LINE_LEN];
3814 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3818 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3822 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3823 *(int *)value == FALSE ? "off" : "on"));
3827 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3830 case TYPE_YES_NO_AUTO:
3831 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3832 *(int *)value == FALSE ? "no" : "yes"));
3836 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3840 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3844 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3848 sprintf(value_string, "%d", *(int *)value);
3852 if (*(char **)value == NULL)
3855 strcpy(value_string, *(char **)value);
3859 value_string[0] = '\0';
3863 if (type & TYPE_GHOSTED)
3864 strcpy(value_string, "n/a");
3866 return value_string;
3869 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3873 static char token_string[MAX_LINE_LEN];
3874 int token_type = token_info[token_nr].type;
3875 void *setup_value = token_info[token_nr].value;
3876 char *token_text = token_info[token_nr].text;
3877 char *value_string = getSetupValue(token_type, setup_value);
3879 /* build complete token string */
3880 sprintf(token_string, "%s%s", prefix, token_text);
3882 /* build setup entry line */
3883 line = getFormattedSetupEntry(token_string, value_string);
3885 if (token_type == TYPE_KEY_X11)
3887 Key key = *(Key *)setup_value;
3888 char *keyname = getKeyNameFromKey(key);
3890 /* add comment, if useful */
3891 if (!strEqual(keyname, "(undefined)") &&
3892 !strEqual(keyname, "(unknown)"))
3894 /* add at least one whitespace */
3896 for (i = strlen(line); i < token_comment_position; i++)
3900 strcat(line, keyname);
3907 void LoadLevelSetup_LastSeries()
3909 /* ----------------------------------------------------------------------- */
3910 /* ~/.<program>/levelsetup.conf */
3911 /* ----------------------------------------------------------------------- */
3913 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3914 SetupFileHash *level_setup_hash = NULL;
3916 /* always start with reliable default values */
3917 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3919 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3920 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3922 if (leveldir_current == NULL)
3923 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3926 if ((level_setup_hash = loadSetupFileHash(filename)))
3928 char *last_level_series =
3929 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3931 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3933 if (leveldir_current == NULL)
3934 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3936 checkSetupFileHashIdentifier(level_setup_hash, filename,
3937 getCookie("LEVELSETUP"));
3939 freeSetupFileHash(level_setup_hash);
3942 Error(ERR_WARN, "using default setup values");
3947 void SaveLevelSetup_LastSeries()
3949 /* ----------------------------------------------------------------------- */
3950 /* ~/.<program>/levelsetup.conf */
3951 /* ----------------------------------------------------------------------- */
3953 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3954 char *level_subdir = leveldir_current->subdir;
3957 InitUserDataDirectory();
3959 if (!(file = fopen(filename, MODE_WRITE)))
3961 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3966 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3967 getCookie("LEVELSETUP")));
3968 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3973 SetFilePermissions(filename, PERMS_PRIVATE);
3978 static void checkSeriesInfo()
3980 static char *level_directory = NULL;
3982 struct dirent *dir_entry;
3984 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3986 level_directory = getPath2((leveldir_current->in_user_dir ?
3987 getUserLevelDir(NULL) :
3988 options.level_directory),
3989 leveldir_current->fullpath);
3991 if ((dir = opendir(level_directory)) == NULL)
3993 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3997 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3999 if (strlen(dir_entry->d_name) > 4 &&
4000 dir_entry->d_name[3] == '.' &&
4001 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4003 char levelnum_str[4];
4006 strncpy(levelnum_str, dir_entry->d_name, 3);
4007 levelnum_str[3] = '\0';
4009 levelnum_value = atoi(levelnum_str);
4012 if (levelnum_value < leveldir_current->first_level)
4014 Error(ERR_WARN, "additional level %d found", levelnum_value);
4015 leveldir_current->first_level = levelnum_value;
4017 else if (levelnum_value > leveldir_current->last_level)
4019 Error(ERR_WARN, "additional level %d found", levelnum_value);
4020 leveldir_current->last_level = levelnum_value;
4029 void LoadLevelSetup_SeriesInfo()
4032 SetupFileHash *level_setup_hash = NULL;
4033 char *level_subdir = leveldir_current->subdir;
4036 /* always start with reliable default values */
4037 level_nr = leveldir_current->first_level;
4039 for (i = 0; i < MAX_LEVELS; i++)
4041 LevelStats_setPlayed(i, 0);
4042 LevelStats_setSolved(i, 0);
4045 checkSeriesInfo(leveldir_current);
4047 /* ----------------------------------------------------------------------- */
4048 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4049 /* ----------------------------------------------------------------------- */
4051 level_subdir = leveldir_current->subdir;
4053 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4055 if ((level_setup_hash = loadSetupFileHash(filename)))
4059 /* get last played level in this level set */
4061 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4065 level_nr = atoi(token_value);
4067 if (level_nr < leveldir_current->first_level)
4068 level_nr = leveldir_current->first_level;
4069 if (level_nr > leveldir_current->last_level)
4070 level_nr = leveldir_current->last_level;
4073 /* get handicap level in this level set */
4075 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4079 int level_nr = atoi(token_value);
4081 if (level_nr < leveldir_current->first_level)
4082 level_nr = leveldir_current->first_level;
4083 if (level_nr > leveldir_current->last_level + 1)
4084 level_nr = leveldir_current->last_level;
4086 if (leveldir_current->user_defined || !leveldir_current->handicap)
4087 level_nr = leveldir_current->last_level;
4089 leveldir_current->handicap_level = level_nr;
4092 /* get number of played and solved levels in this level set */
4094 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4096 char *token = HASH_ITERATION_TOKEN(itr);
4097 char *value = HASH_ITERATION_VALUE(itr);
4099 if (strlen(token) == 3 &&
4100 token[0] >= '0' && token[0] <= '9' &&
4101 token[1] >= '0' && token[1] <= '9' &&
4102 token[2] >= '0' && token[2] <= '9')
4104 int level_nr = atoi(token);
4107 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4109 value = strchr(value, ' ');
4112 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4115 END_HASH_ITERATION(hash, itr)
4117 checkSetupFileHashIdentifier(level_setup_hash, filename,
4118 getCookie("LEVELSETUP"));
4120 freeSetupFileHash(level_setup_hash);
4123 Error(ERR_WARN, "using default setup values");
4128 void SaveLevelSetup_SeriesInfo()
4131 char *level_subdir = leveldir_current->subdir;
4132 char *level_nr_str = int2str(level_nr, 0);
4133 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4137 /* ----------------------------------------------------------------------- */
4138 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4139 /* ----------------------------------------------------------------------- */
4141 InitLevelSetupDirectory(level_subdir);
4143 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4145 if (!(file = fopen(filename, MODE_WRITE)))
4147 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4152 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4153 getCookie("LEVELSETUP")));
4154 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4156 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4157 handicap_level_str));
4159 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4162 if (LevelStats_getPlayed(i) > 0 ||
4163 LevelStats_getSolved(i) > 0)
4168 sprintf(token, "%03d", i);
4169 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4171 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4177 SetFilePermissions(filename, PERMS_PRIVATE);
4182 int LevelStats_getPlayed(int nr)
4184 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4187 int LevelStats_getSolved(int nr)
4189 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4192 void LevelStats_setPlayed(int nr, int value)
4194 if (nr >= 0 && nr < MAX_LEVELS)
4195 level_stats[nr].played = value;
4198 void LevelStats_setSolved(int nr, int value)
4200 if (nr >= 0 && nr < MAX_LEVELS)
4201 level_stats[nr].solved = value;
4204 void LevelStats_incPlayed(int nr)
4206 if (nr >= 0 && nr < MAX_LEVELS)
4207 level_stats[nr].played++;
4210 void LevelStats_incSolved(int nr)
4212 if (nr >= 0 && nr < MAX_LEVELS)
4213 level_stats[nr].solved++;