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);
1634 static void printSetupFileList(SetupFileList *list)
1639 printf("token: '%s'\n", list->token);
1640 printf("value: '%s'\n", list->value);
1642 printSetupFileList(list->next);
1647 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1648 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1649 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1650 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1652 #define insert_hash_entry hashtable_insert
1653 #define search_hash_entry hashtable_search
1654 #define change_hash_entry hashtable_change
1655 #define remove_hash_entry hashtable_remove
1658 unsigned int get_hash_from_key(void *key)
1663 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1664 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1665 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1666 it works better than many other constants, prime or not) has never been
1667 adequately explained.
1669 If you just want to have a good hash function, and cannot wait, djb2
1670 is one of the best string hash functions i know. It has excellent
1671 distribution and speed on many different sets of keys and table sizes.
1672 You are not likely to do better with one of the "well known" functions
1673 such as PJW, K&R, etc.
1675 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1678 char *str = (char *)key;
1679 unsigned int hash = 5381;
1682 while ((c = *str++))
1683 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1688 static int keys_are_equal(void *key1, void *key2)
1690 return (strEqual((char *)key1, (char *)key2));
1693 SetupFileHash *newSetupFileHash()
1695 SetupFileHash *new_hash =
1696 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1698 if (new_hash == NULL)
1699 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1704 void freeSetupFileHash(SetupFileHash *hash)
1709 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1712 char *getHashEntry(SetupFileHash *hash, char *token)
1717 return search_hash_entry(hash, token);
1720 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1727 value_copy = getStringCopy(value);
1729 /* change value; if it does not exist, insert it as new */
1730 if (!change_hash_entry(hash, token, value_copy))
1731 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1732 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1735 char *removeHashEntry(SetupFileHash *hash, char *token)
1740 return remove_hash_entry(hash, token);
1744 static void printSetupFileHash(SetupFileHash *hash)
1746 BEGIN_HASH_ITERATION(hash, itr)
1748 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1749 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1751 END_HASH_ITERATION(hash, itr)
1755 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1756 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1757 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1759 static boolean token_value_separator_found = FALSE;
1760 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1761 static boolean token_value_separator_warning = FALSE;
1763 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1764 static boolean token_already_exists_warning = FALSE;
1767 static boolean getTokenValueFromSetupLineExt(char *line,
1768 char **token_ptr, char **value_ptr,
1769 char *filename, char *line_raw,
1771 boolean separator_required)
1773 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1774 char *token, *value, *line_ptr;
1776 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1777 if (line_raw == NULL)
1779 strncpy(line_copy, line, MAX_LINE_LEN);
1780 line_copy[MAX_LINE_LEN] = '\0';
1783 strcpy(line_raw_copy, line_copy);
1784 line_raw = line_raw_copy;
1787 /* cut trailing comment from input line */
1788 for (line_ptr = line; *line_ptr; line_ptr++)
1790 if (*line_ptr == '#')
1797 /* cut trailing whitespaces from input line */
1798 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1799 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1802 /* ignore empty lines */
1806 /* cut leading whitespaces from token */
1807 for (token = line; *token; token++)
1808 if (*token != ' ' && *token != '\t')
1811 /* start with empty value as reliable default */
1814 token_value_separator_found = FALSE;
1816 /* find end of token to determine start of value */
1817 for (line_ptr = token; *line_ptr; line_ptr++)
1820 /* first look for an explicit token/value separator, like ':' or '=' */
1821 if (*line_ptr == ':' || *line_ptr == '=')
1823 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1826 *line_ptr = '\0'; /* terminate token string */
1827 value = line_ptr + 1; /* set beginning of value */
1829 token_value_separator_found = TRUE;
1835 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1836 /* fallback: if no token/value separator found, also allow whitespaces */
1837 if (!token_value_separator_found && !separator_required)
1839 for (line_ptr = token; *line_ptr; line_ptr++)
1841 if (*line_ptr == ' ' || *line_ptr == '\t')
1843 *line_ptr = '\0'; /* terminate token string */
1844 value = line_ptr + 1; /* set beginning of value */
1846 token_value_separator_found = TRUE;
1852 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1853 if (token_value_separator_found)
1855 if (!token_value_separator_warning)
1857 Error(ERR_INFO_LINE, "-");
1859 if (filename != NULL)
1861 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1862 Error(ERR_INFO, "- config file: '%s'", filename);
1866 Error(ERR_WARN, "missing token/value separator(s):");
1869 token_value_separator_warning = TRUE;
1872 if (filename != NULL)
1873 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1875 Error(ERR_INFO, "- line: '%s'", line_raw);
1881 /* cut trailing whitespaces from token */
1882 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1883 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1886 /* cut leading whitespaces from value */
1887 for (; *value; value++)
1888 if (*value != ' ' && *value != '\t')
1893 value = "true"; /* treat tokens without value as "true" */
1902 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1904 /* while the internal (old) interface does not require a token/value
1905 separator (for downwards compatibility with existing files which
1906 don't use them), it is mandatory for the external (new) interface */
1908 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1912 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1913 boolean top_recursion_level, boolean is_hash)
1915 static SetupFileHash *include_filename_hash = NULL;
1916 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1917 char *token, *value, *line_ptr;
1918 void *insert_ptr = NULL;
1919 boolean read_continued_line = FALSE;
1921 int line_nr = 0, token_count = 0, include_count = 0;
1923 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1924 token_value_separator_warning = FALSE;
1927 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1928 token_already_exists_warning = FALSE;
1931 if (!(file = fopen(filename, MODE_READ)))
1933 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1938 /* use "insert pointer" to store list end for constant insertion complexity */
1940 insert_ptr = setup_file_data;
1942 /* on top invocation, create hash to mark included files (to prevent loops) */
1943 if (top_recursion_level)
1944 include_filename_hash = newSetupFileHash();
1946 /* mark this file as already included (to prevent including it again) */
1947 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1951 /* read next line of input file */
1952 if (!fgets(line, MAX_LINE_LEN, file))
1955 /* check if line was completely read and is terminated by line break */
1956 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1959 /* cut trailing line break (this can be newline and/or carriage return) */
1960 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1961 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1964 /* copy raw input line for later use (mainly debugging output) */
1965 strcpy(line_raw, line);
1967 if (read_continued_line)
1970 /* !!! ??? WHY ??? !!! */
1971 /* cut leading whitespaces from input line */
1972 for (line_ptr = line; *line_ptr; line_ptr++)
1973 if (*line_ptr != ' ' && *line_ptr != '\t')
1977 /* append new line to existing line, if there is enough space */
1978 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1979 strcat(previous_line, line_ptr);
1981 strcpy(line, previous_line); /* copy storage buffer to line */
1983 read_continued_line = FALSE;
1986 /* if the last character is '\', continue at next line */
1987 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1989 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1990 strcpy(previous_line, line); /* copy line to storage buffer */
1992 read_continued_line = TRUE;
1997 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1998 line_raw, line_nr, FALSE))
2003 if (strEqual(token, "include"))
2005 if (getHashEntry(include_filename_hash, value) == NULL)
2007 char *basepath = getBasePath(filename);
2008 char *basename = getBaseName(value);
2009 char *filename_include = getPath2(basepath, basename);
2012 Error(ERR_INFO, "[including file '%s']", filename_include);
2015 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2019 free(filename_include);
2025 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2032 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2034 getHashEntry((SetupFileHash *)setup_file_data, token);
2036 if (old_value != NULL)
2038 if (!token_already_exists_warning)
2040 Error(ERR_INFO_LINE, "-");
2041 Error(ERR_WARN, "duplicate token(s) found in config file:");
2042 Error(ERR_INFO, "- config file: '%s'", filename);
2044 token_already_exists_warning = TRUE;
2047 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2048 Error(ERR_INFO, " old value: '%s'", old_value);
2049 Error(ERR_INFO, " new value: '%s'", value);
2053 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2057 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2067 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2068 if (token_value_separator_warning)
2069 Error(ERR_INFO_LINE, "-");
2072 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2073 if (token_already_exists_warning)
2074 Error(ERR_INFO_LINE, "-");
2077 if (token_count == 0 && include_count == 0)
2078 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2080 if (top_recursion_level)
2081 freeSetupFileHash(include_filename_hash);
2088 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2089 boolean top_recursion_level, boolean is_hash)
2091 static SetupFileHash *include_filename_hash = NULL;
2092 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2093 char *token, *value, *line_ptr;
2094 void *insert_ptr = NULL;
2095 boolean read_continued_line = FALSE;
2098 int token_count = 0;
2100 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2101 token_value_separator_warning = FALSE;
2104 if (!(file = fopen(filename, MODE_READ)))
2106 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2111 /* use "insert pointer" to store list end for constant insertion complexity */
2113 insert_ptr = setup_file_data;
2115 /* on top invocation, create hash to mark included files (to prevent loops) */
2116 if (top_recursion_level)
2117 include_filename_hash = newSetupFileHash();
2119 /* mark this file as already included (to prevent including it again) */
2120 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2124 /* read next line of input file */
2125 if (!fgets(line, MAX_LINE_LEN, file))
2128 /* check if line was completely read and is terminated by line break */
2129 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2132 /* cut trailing line break (this can be newline and/or carriage return) */
2133 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2134 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2137 /* copy raw input line for later use (mainly debugging output) */
2138 strcpy(line_raw, line);
2140 if (read_continued_line)
2142 /* cut leading whitespaces from input line */
2143 for (line_ptr = line; *line_ptr; line_ptr++)
2144 if (*line_ptr != ' ' && *line_ptr != '\t')
2147 /* append new line to existing line, if there is enough space */
2148 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2149 strcat(previous_line, line_ptr);
2151 strcpy(line, previous_line); /* copy storage buffer to line */
2153 read_continued_line = FALSE;
2156 /* if the last character is '\', continue at next line */
2157 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2159 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2160 strcpy(previous_line, line); /* copy line to storage buffer */
2162 read_continued_line = TRUE;
2167 /* cut trailing comment from input line */
2168 for (line_ptr = line; *line_ptr; line_ptr++)
2170 if (*line_ptr == '#')
2177 /* cut trailing whitespaces from input line */
2178 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2179 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2182 /* ignore empty lines */
2186 /* cut leading whitespaces from token */
2187 for (token = line; *token; token++)
2188 if (*token != ' ' && *token != '\t')
2191 /* start with empty value as reliable default */
2194 token_value_separator_found = FALSE;
2196 /* find end of token to determine start of value */
2197 for (line_ptr = token; *line_ptr; line_ptr++)
2200 /* first look for an explicit token/value separator, like ':' or '=' */
2201 if (*line_ptr == ':' || *line_ptr == '=')
2203 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2206 *line_ptr = '\0'; /* terminate token string */
2207 value = line_ptr + 1; /* set beginning of value */
2209 token_value_separator_found = TRUE;
2215 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2216 /* fallback: if no token/value separator found, also allow whitespaces */
2217 if (!token_value_separator_found)
2219 for (line_ptr = token; *line_ptr; line_ptr++)
2221 if (*line_ptr == ' ' || *line_ptr == '\t')
2223 *line_ptr = '\0'; /* terminate token string */
2224 value = line_ptr + 1; /* set beginning of value */
2226 token_value_separator_found = TRUE;
2232 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2233 if (token_value_separator_found)
2235 if (!token_value_separator_warning)
2237 Error(ERR_INFO_LINE, "-");
2238 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2239 Error(ERR_INFO, "- config file: '%s'", filename);
2241 token_value_separator_warning = TRUE;
2244 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2250 /* cut trailing whitespaces from token */
2251 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2252 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2255 /* cut leading whitespaces from value */
2256 for (; *value; value++)
2257 if (*value != ' ' && *value != '\t')
2262 value = "true"; /* treat tokens without value as "true" */
2267 if (strEqual(token, "include"))
2269 if (getHashEntry(include_filename_hash, value) == NULL)
2271 char *basepath = getBasePath(filename);
2272 char *basename = getBaseName(value);
2273 char *filename_include = getPath2(basepath, basename);
2276 Error(ERR_INFO, "[including file '%s']", filename_include);
2279 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2283 free(filename_include);
2287 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2293 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2295 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2304 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2305 if (token_value_separator_warning)
2306 Error(ERR_INFO_LINE, "-");
2309 if (token_count == 0)
2310 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2312 if (top_recursion_level)
2313 freeSetupFileHash(include_filename_hash);
2319 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2323 if (!(file = fopen(filename, MODE_WRITE)))
2325 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2330 BEGIN_HASH_ITERATION(hash, itr)
2332 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2333 HASH_ITERATION_VALUE(itr)));
2335 END_HASH_ITERATION(hash, itr)
2340 SetupFileList *loadSetupFileList(char *filename)
2342 SetupFileList *setup_file_list = newSetupFileList("", "");
2343 SetupFileList *first_valid_list_entry;
2345 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2347 freeSetupFileList(setup_file_list);
2352 first_valid_list_entry = setup_file_list->next;
2354 /* free empty list header */
2355 setup_file_list->next = NULL;
2356 freeSetupFileList(setup_file_list);
2358 return first_valid_list_entry;
2361 SetupFileHash *loadSetupFileHash(char *filename)
2363 SetupFileHash *setup_file_hash = newSetupFileHash();
2365 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2367 freeSetupFileHash(setup_file_hash);
2372 return setup_file_hash;
2375 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2376 char *filename, char *identifier)
2378 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2381 Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2382 else if (!checkCookieString(value, identifier))
2383 Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2387 /* ========================================================================= */
2388 /* setup file stuff */
2389 /* ========================================================================= */
2391 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2392 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2393 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2395 /* level directory info */
2396 #define LEVELINFO_TOKEN_IDENTIFIER 0
2397 #define LEVELINFO_TOKEN_NAME 1
2398 #define LEVELINFO_TOKEN_NAME_SORTING 2
2399 #define LEVELINFO_TOKEN_AUTHOR 3
2400 #define LEVELINFO_TOKEN_YEAR 4
2401 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2402 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2403 #define LEVELINFO_TOKEN_TESTED_BY 7
2404 #define LEVELINFO_TOKEN_LEVELS 8
2405 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2406 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2407 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2408 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2409 #define LEVELINFO_TOKEN_READONLY 13
2410 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2411 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2412 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2413 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2414 #define LEVELINFO_TOKEN_MUSIC_SET 18
2415 #define LEVELINFO_TOKEN_FILENAME 19
2416 #define LEVELINFO_TOKEN_FILETYPE 20
2417 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2418 #define LEVELINFO_TOKEN_HANDICAP 22
2419 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2421 #define NUM_LEVELINFO_TOKENS 24
2423 static LevelDirTree ldi;
2425 static struct TokenInfo levelinfo_tokens[] =
2427 /* level directory info */
2428 { TYPE_STRING, &ldi.identifier, "identifier" },
2429 { TYPE_STRING, &ldi.name, "name" },
2430 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2431 { TYPE_STRING, &ldi.author, "author" },
2432 { TYPE_STRING, &ldi.year, "year" },
2433 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2434 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2435 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2436 { TYPE_INTEGER, &ldi.levels, "levels" },
2437 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2438 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2439 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2440 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2441 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2442 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2443 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2444 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2445 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2446 { TYPE_STRING, &ldi.music_set, "music_set" },
2447 { TYPE_STRING, &ldi.level_filename, "filename" },
2448 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2449 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2450 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2451 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2454 static struct TokenInfo artworkinfo_tokens[] =
2456 /* artwork directory info */
2457 { TYPE_STRING, &ldi.identifier, "identifier" },
2458 { TYPE_STRING, &ldi.subdir, "subdir" },
2459 { TYPE_STRING, &ldi.name, "name" },
2460 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2461 { TYPE_STRING, &ldi.author, "author" },
2462 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2463 { TYPE_STRING, &ldi.basepath, "basepath" },
2464 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2465 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2466 { TYPE_INTEGER, &ldi.color, "color" },
2467 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2472 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2476 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2477 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2478 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2479 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2482 ti->node_parent = NULL;
2483 ti->node_group = NULL;
2490 ti->fullpath = NULL;
2491 ti->basepath = NULL;
2492 ti->identifier = NULL;
2493 ti->name = getStringCopy(ANONYMOUS_NAME);
2494 ti->name_sorting = NULL;
2495 ti->author = getStringCopy(ANONYMOUS_NAME);
2498 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2499 ti->latest_engine = FALSE; /* default: get from level */
2500 ti->parent_link = FALSE;
2501 ti->in_user_dir = FALSE;
2502 ti->user_defined = FALSE;
2504 ti->class_desc = NULL;
2506 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2508 if (ti->type == TREE_TYPE_LEVEL_DIR)
2510 ti->imported_from = NULL;
2511 ti->imported_by = NULL;
2512 ti->tested_by = NULL;
2514 ti->graphics_set_ecs = NULL;
2515 ti->graphics_set_aga = NULL;
2516 ti->graphics_set = NULL;
2517 ti->sounds_set = NULL;
2518 ti->music_set = NULL;
2519 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2520 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2521 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2523 ti->level_filename = NULL;
2524 ti->level_filetype = NULL;
2526 ti->special_flags = NULL;
2529 ti->first_level = 0;
2531 ti->level_group = FALSE;
2532 ti->handicap_level = 0;
2533 ti->readonly = TRUE;
2534 ti->handicap = TRUE;
2535 ti->skip_levels = FALSE;
2539 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2543 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2545 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2550 /* copy all values from the parent structure */
2552 ti->type = parent->type;
2554 ti->node_top = parent->node_top;
2555 ti->node_parent = parent;
2556 ti->node_group = NULL;
2563 ti->fullpath = NULL;
2564 ti->basepath = NULL;
2565 ti->identifier = NULL;
2566 ti->name = getStringCopy(ANONYMOUS_NAME);
2567 ti->name_sorting = NULL;
2568 ti->author = getStringCopy(parent->author);
2569 ti->year = getStringCopy(parent->year);
2571 ti->sort_priority = parent->sort_priority;
2572 ti->latest_engine = parent->latest_engine;
2573 ti->parent_link = FALSE;
2574 ti->in_user_dir = parent->in_user_dir;
2575 ti->user_defined = parent->user_defined;
2576 ti->color = parent->color;
2577 ti->class_desc = getStringCopy(parent->class_desc);
2579 ti->infotext = getStringCopy(parent->infotext);
2581 if (ti->type == TREE_TYPE_LEVEL_DIR)
2583 ti->imported_from = getStringCopy(parent->imported_from);
2584 ti->imported_by = getStringCopy(parent->imported_by);
2585 ti->tested_by = getStringCopy(parent->tested_by);
2587 ti->graphics_set_ecs = NULL;
2588 ti->graphics_set_aga = NULL;
2589 ti->graphics_set = NULL;
2590 ti->sounds_set = NULL;
2591 ti->music_set = NULL;
2592 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2593 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2594 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2596 ti->level_filename = NULL;
2597 ti->level_filetype = NULL;
2599 ti->special_flags = getStringCopy(parent->special_flags);
2602 ti->first_level = 0;
2604 ti->level_group = FALSE;
2605 ti->handicap_level = 0;
2607 ti->readonly = parent->readonly;
2609 ti->readonly = TRUE;
2611 ti->handicap = TRUE;
2612 ti->skip_levels = FALSE;
2616 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2618 TreeInfo *ti_copy = newTreeInfo();
2620 /* copy all values from the original structure */
2622 ti_copy->type = ti->type;
2624 ti_copy->node_top = ti->node_top;
2625 ti_copy->node_parent = ti->node_parent;
2626 ti_copy->node_group = ti->node_group;
2627 ti_copy->next = ti->next;
2629 ti_copy->cl_first = ti->cl_first;
2630 ti_copy->cl_cursor = ti->cl_cursor;
2632 ti_copy->subdir = getStringCopy(ti->subdir);
2633 ti_copy->fullpath = getStringCopy(ti->fullpath);
2634 ti_copy->basepath = getStringCopy(ti->basepath);
2635 ti_copy->identifier = getStringCopy(ti->identifier);
2636 ti_copy->name = getStringCopy(ti->name);
2637 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2638 ti_copy->author = getStringCopy(ti->author);
2639 ti_copy->year = getStringCopy(ti->year);
2640 ti_copy->imported_from = getStringCopy(ti->imported_from);
2641 ti_copy->imported_by = getStringCopy(ti->imported_by);
2642 ti_copy->tested_by = getStringCopy(ti->tested_by);
2644 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2645 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2646 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2647 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2648 ti_copy->music_set = getStringCopy(ti->music_set);
2649 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2650 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2651 ti_copy->music_path = getStringCopy(ti->music_path);
2653 ti_copy->level_filename = getStringCopy(ti->level_filename);
2654 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2656 ti_copy->special_flags = getStringCopy(ti->special_flags);
2658 ti_copy->levels = ti->levels;
2659 ti_copy->first_level = ti->first_level;
2660 ti_copy->last_level = ti->last_level;
2661 ti_copy->sort_priority = ti->sort_priority;
2663 ti_copy->latest_engine = ti->latest_engine;
2665 ti_copy->level_group = ti->level_group;
2666 ti_copy->parent_link = ti->parent_link;
2667 ti_copy->in_user_dir = ti->in_user_dir;
2668 ti_copy->user_defined = ti->user_defined;
2669 ti_copy->readonly = ti->readonly;
2670 ti_copy->handicap = ti->handicap;
2671 ti_copy->skip_levels = ti->skip_levels;
2673 ti_copy->color = ti->color;
2674 ti_copy->class_desc = getStringCopy(ti->class_desc);
2675 ti_copy->handicap_level = ti->handicap_level;
2677 ti_copy->infotext = getStringCopy(ti->infotext);
2682 void freeTreeInfo(TreeInfo *ti)
2687 checked_free(ti->subdir);
2688 checked_free(ti->fullpath);
2689 checked_free(ti->basepath);
2690 checked_free(ti->identifier);
2692 checked_free(ti->name);
2693 checked_free(ti->name_sorting);
2694 checked_free(ti->author);
2695 checked_free(ti->year);
2697 checked_free(ti->class_desc);
2699 checked_free(ti->infotext);
2701 if (ti->type == TREE_TYPE_LEVEL_DIR)
2703 checked_free(ti->imported_from);
2704 checked_free(ti->imported_by);
2705 checked_free(ti->tested_by);
2707 checked_free(ti->graphics_set_ecs);
2708 checked_free(ti->graphics_set_aga);
2709 checked_free(ti->graphics_set);
2710 checked_free(ti->sounds_set);
2711 checked_free(ti->music_set);
2713 checked_free(ti->graphics_path);
2714 checked_free(ti->sounds_path);
2715 checked_free(ti->music_path);
2717 checked_free(ti->level_filename);
2718 checked_free(ti->level_filetype);
2720 checked_free(ti->special_flags);
2726 void setSetupInfo(struct TokenInfo *token_info,
2727 int token_nr, char *token_value)
2729 int token_type = token_info[token_nr].type;
2730 void *setup_value = token_info[token_nr].value;
2732 if (token_value == NULL)
2735 /* set setup field to corresponding token value */
2740 *(boolean *)setup_value = get_boolean_from_string(token_value);
2744 *(int *)setup_value = get_switch3_from_string(token_value);
2748 *(Key *)setup_value = getKeyFromKeyName(token_value);
2752 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2756 *(int *)setup_value = get_integer_from_string(token_value);
2760 checked_free(*(char **)setup_value);
2761 *(char **)setup_value = getStringCopy(token_value);
2769 static int compareTreeInfoEntries(const void *object1, const void *object2)
2771 const TreeInfo *entry1 = *((TreeInfo **)object1);
2772 const TreeInfo *entry2 = *((TreeInfo **)object2);
2773 int class_sorting1, class_sorting2;
2776 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2778 class_sorting1 = LEVELSORTING(entry1);
2779 class_sorting2 = LEVELSORTING(entry2);
2783 class_sorting1 = ARTWORKSORTING(entry1);
2784 class_sorting2 = ARTWORKSORTING(entry2);
2787 if (entry1->parent_link || entry2->parent_link)
2788 compare_result = (entry1->parent_link ? -1 : +1);
2789 else if (entry1->sort_priority == entry2->sort_priority)
2791 char *name1 = getStringToLower(entry1->name_sorting);
2792 char *name2 = getStringToLower(entry2->name_sorting);
2794 compare_result = strcmp(name1, name2);
2799 else if (class_sorting1 == class_sorting2)
2800 compare_result = entry1->sort_priority - entry2->sort_priority;
2802 compare_result = class_sorting1 - class_sorting2;
2804 return compare_result;
2807 static void createParentTreeInfoNode(TreeInfo *node_parent)
2811 if (node_parent == NULL)
2814 ti_new = newTreeInfo();
2815 setTreeInfoToDefaults(ti_new, node_parent->type);
2817 ti_new->node_parent = node_parent;
2818 ti_new->parent_link = TRUE;
2820 setString(&ti_new->identifier, node_parent->identifier);
2821 setString(&ti_new->name, ".. (parent directory)");
2822 setString(&ti_new->name_sorting, ti_new->name);
2824 setString(&ti_new->subdir, "..");
2825 setString(&ti_new->fullpath, node_parent->fullpath);
2827 ti_new->sort_priority = node_parent->sort_priority;
2828 ti_new->latest_engine = node_parent->latest_engine;
2830 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2832 pushTreeInfo(&node_parent->node_group, ti_new);
2836 /* -------------------------------------------------------------------------- */
2837 /* functions for handling level and custom artwork info cache */
2838 /* -------------------------------------------------------------------------- */
2840 static void LoadArtworkInfoCache()
2842 InitCacheDirectory();
2844 if (artworkinfo_cache_old == NULL)
2846 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2848 /* try to load artwork info hash from already existing cache file */
2849 artworkinfo_cache_old = loadSetupFileHash(filename);
2851 /* if no artwork info cache file was found, start with empty hash */
2852 if (artworkinfo_cache_old == NULL)
2853 artworkinfo_cache_old = newSetupFileHash();
2858 if (artworkinfo_cache_new == NULL)
2859 artworkinfo_cache_new = newSetupFileHash();
2862 static void SaveArtworkInfoCache()
2864 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2866 InitCacheDirectory();
2868 saveSetupFileHash(artworkinfo_cache_new, filename);
2873 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2875 static char *prefix = NULL;
2877 checked_free(prefix);
2879 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2884 /* (identical to above function, but separate string buffer needed -- nasty) */
2885 static char *getCacheToken(char *prefix, char *suffix)
2887 static char *token = NULL;
2889 checked_free(token);
2891 token = getStringCat2WithSeparator(prefix, suffix, ".");
2896 static char *getFileTimestampString(char *filename)
2899 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2901 struct stat file_status;
2903 if (stat(filename, &file_status) != 0) /* cannot stat file */
2904 return getStringCopy(i_to_a(0));
2906 return getStringCopy(i_to_a(file_status.st_mtime));
2910 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2912 struct stat file_status;
2914 if (timestamp_string == NULL)
2917 if (stat(filename, &file_status) != 0) /* cannot stat file */
2920 return (file_status.st_mtime != atoi(timestamp_string));
2923 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2925 char *identifier = level_node->subdir;
2926 char *type_string = ARTWORK_DIRECTORY(type);
2927 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2928 char *token_main = getCacheToken(token_prefix, "CACHED");
2929 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2930 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2931 TreeInfo *artwork_info = NULL;
2933 if (!use_artworkinfo_cache)
2940 artwork_info = newTreeInfo();
2941 setTreeInfoToDefaults(artwork_info, type);
2943 /* set all structure fields according to the token/value pairs */
2944 ldi = *artwork_info;
2945 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2947 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2948 char *value = getHashEntry(artworkinfo_cache_old, token);
2950 setSetupInfo(artworkinfo_tokens, i, value);
2952 /* check if cache entry for this item is invalid or incomplete */
2956 Error(ERR_WARN, "cache entry '%s' invalid", token);
2963 *artwork_info = ldi;
2968 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2969 LEVELINFO_FILENAME);
2970 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2971 ARTWORKINFO_FILENAME(type));
2973 /* check if corresponding "levelinfo.conf" file has changed */
2974 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2975 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2977 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2980 /* check if corresponding "<artworkinfo>.conf" file has changed */
2981 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2982 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2984 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2989 printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2992 checked_free(filename_levelinfo);
2993 checked_free(filename_artworkinfo);
2996 if (!cached && artwork_info != NULL)
2998 freeTreeInfo(artwork_info);
3003 return artwork_info;
3006 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3007 LevelDirTree *level_node, int type)
3009 char *identifier = level_node->subdir;
3010 char *type_string = ARTWORK_DIRECTORY(type);
3011 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3012 char *token_main = getCacheToken(token_prefix, "CACHED");
3013 boolean set_cache_timestamps = TRUE;
3016 setHashEntry(artworkinfo_cache_new, token_main, "true");
3018 if (set_cache_timestamps)
3020 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3021 LEVELINFO_FILENAME);
3022 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3023 ARTWORKINFO_FILENAME(type));
3024 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3025 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3027 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3028 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3030 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3031 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3033 checked_free(filename_levelinfo);
3034 checked_free(filename_artworkinfo);
3035 checked_free(timestamp_levelinfo);
3036 checked_free(timestamp_artworkinfo);
3039 ldi = *artwork_info;
3040 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3042 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3043 char *value = getSetupValue(artworkinfo_tokens[i].type,
3044 artworkinfo_tokens[i].value);
3046 setHashEntry(artworkinfo_cache_new, token, value);
3051 /* -------------------------------------------------------------------------- */
3052 /* functions for loading level info and custom artwork info */
3053 /* -------------------------------------------------------------------------- */
3055 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3056 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3058 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3059 TreeInfo *node_parent,
3060 char *level_directory,
3061 char *directory_name)
3064 static unsigned long progress_delay = 0;
3065 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3067 char *directory_path = getPath2(level_directory, directory_name);
3068 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3069 SetupFileHash *setup_file_hash;
3070 LevelDirTree *leveldir_new = NULL;
3073 /* unless debugging, silently ignore directories without "levelinfo.conf" */
3074 if (!options.debug && !fileExists(filename))
3076 free(directory_path);
3082 setup_file_hash = loadSetupFileHash(filename);
3084 if (setup_file_hash == NULL)
3086 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3088 free(directory_path);
3094 leveldir_new = newTreeInfo();
3097 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3099 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3101 leveldir_new->subdir = getStringCopy(directory_name);
3103 checkSetupFileHashIdentifier(setup_file_hash, filename,
3104 getCookie("LEVELINFO"));
3106 /* set all structure fields according to the token/value pairs */
3107 ldi = *leveldir_new;
3108 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3109 setSetupInfo(levelinfo_tokens, i,
3110 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3111 *leveldir_new = ldi;
3113 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3114 setString(&leveldir_new->name, leveldir_new->subdir);
3116 if (leveldir_new->identifier == NULL)
3117 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3119 if (leveldir_new->name_sorting == NULL)
3120 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3122 if (node_parent == NULL) /* top level group */
3124 leveldir_new->basepath = getStringCopy(level_directory);
3125 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3127 else /* sub level group */
3129 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3130 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3134 if (leveldir_new->levels < 1)
3135 leveldir_new->levels = 1;
3138 leveldir_new->last_level =
3139 leveldir_new->first_level + leveldir_new->levels - 1;
3141 leveldir_new->in_user_dir =
3142 (!strEqual(leveldir_new->basepath, options.level_directory));
3145 printf("::: '%s' -> %d\n",
3146 leveldir_new->identifier,
3147 leveldir_new->in_user_dir);
3150 /* adjust some settings if user's private level directory was detected */
3151 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3152 leveldir_new->in_user_dir &&
3153 (strEqual(leveldir_new->subdir, getLoginName()) ||
3154 strEqual(leveldir_new->name, getLoginName()) ||
3155 strEqual(leveldir_new->author, getRealName())))
3157 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3158 leveldir_new->readonly = FALSE;
3161 leveldir_new->user_defined =
3162 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3164 leveldir_new->color = LEVELCOLOR(leveldir_new);
3166 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3168 leveldir_new->handicap_level = /* set handicap to default value */
3169 (leveldir_new->user_defined || !leveldir_new->handicap ?
3170 leveldir_new->last_level : leveldir_new->first_level);
3174 DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3175 leveldir_new->level_group);
3177 if (leveldir_new->level_group ||
3178 DelayReached(&progress_delay, progress_delay_value))
3179 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3182 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3186 /* !!! don't skip sets without levels (else artwork base sets are missing) */
3188 if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3190 /* skip level sets without levels (which are probably artwork base sets) */
3192 freeSetupFileHash(setup_file_hash);
3193 free(directory_path);
3201 pushTreeInfo(node_first, leveldir_new);
3203 freeSetupFileHash(setup_file_hash);
3205 if (leveldir_new->level_group)
3207 /* create node to link back to current level directory */
3208 createParentTreeInfoNode(leveldir_new);
3210 /* recursively step into sub-directory and look for more level series */
3211 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3212 leveldir_new, directory_path);
3215 free(directory_path);
3221 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3222 TreeInfo *node_parent,
3223 char *level_directory)
3226 struct dirent *dir_entry;
3227 boolean valid_entry_found = FALSE;
3229 if ((dir = opendir(level_directory)) == NULL)
3231 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3235 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3237 struct stat file_status;
3238 char *directory_name = dir_entry->d_name;
3239 char *directory_path = getPath2(level_directory, directory_name);
3241 /* skip entries for current and parent directory */
3242 if (strEqual(directory_name, ".") ||
3243 strEqual(directory_name, ".."))
3245 free(directory_path);
3249 /* find out if directory entry is itself a directory */
3250 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3251 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3253 free(directory_path);
3257 free(directory_path);
3259 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3260 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3261 strEqual(directory_name, MUSIC_DIRECTORY))
3264 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3271 /* special case: top level directory may directly contain "levelinfo.conf" */
3272 if (node_parent == NULL && !valid_entry_found)
3274 /* check if this directory directly contains a file "levelinfo.conf" */
3275 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3276 level_directory, ".");
3279 if (!valid_entry_found)
3280 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3284 boolean AdjustGraphicsForEMC()
3286 boolean settings_changed = FALSE;
3288 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3289 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3291 return settings_changed;
3294 void LoadLevelInfo()
3296 InitUserLevelDirectory(getLoginName());
3298 DrawInitText("Loading level series", 120, FC_GREEN);
3300 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3301 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3303 /* after loading all level set information, clone the level directory tree
3304 and remove all level sets without levels (these may still contain artwork
3305 to be offered in the setup menu as "custom artwork", and are therefore
3306 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3307 leveldir_first_all = leveldir_first;
3308 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3310 AdjustGraphicsForEMC();
3312 /* before sorting, the first entries will be from the user directory */
3313 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3315 if (leveldir_first == NULL)
3316 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3318 sortTreeInfo(&leveldir_first);
3321 dumpTreeInfo(leveldir_first, 0);
3325 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3326 TreeInfo *node_parent,
3327 char *base_directory,
3328 char *directory_name, int type)
3330 char *directory_path = getPath2(base_directory, directory_name);
3331 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3332 SetupFileHash *setup_file_hash = NULL;
3333 TreeInfo *artwork_new = NULL;
3336 if (fileExists(filename))
3337 setup_file_hash = loadSetupFileHash(filename);
3339 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3342 struct dirent *dir_entry;
3343 boolean valid_file_found = FALSE;
3345 if ((dir = opendir(directory_path)) != NULL)
3347 while ((dir_entry = readdir(dir)) != NULL)
3349 char *entry_name = dir_entry->d_name;
3351 if (FileIsArtworkType(entry_name, type))
3353 valid_file_found = TRUE;
3361 if (!valid_file_found)
3363 if (!strEqual(directory_name, "."))
3364 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3366 free(directory_path);
3373 artwork_new = newTreeInfo();
3376 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3378 setTreeInfoToDefaults(artwork_new, type);
3380 artwork_new->subdir = getStringCopy(directory_name);
3382 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3385 checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3388 /* set all structure fields according to the token/value pairs */
3390 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3391 setSetupInfo(levelinfo_tokens, i,
3392 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3395 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3396 setString(&artwork_new->name, artwork_new->subdir);
3398 if (artwork_new->identifier == NULL)
3399 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3401 if (artwork_new->name_sorting == NULL)
3402 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3405 if (node_parent == NULL) /* top level group */
3407 artwork_new->basepath = getStringCopy(base_directory);
3408 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3410 else /* sub level group */
3412 artwork_new->basepath = getStringCopy(node_parent->basepath);
3413 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3416 artwork_new->in_user_dir =
3417 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3419 /* (may use ".sort_priority" from "setup_file_hash" above) */
3420 artwork_new->color = ARTWORKCOLOR(artwork_new);
3422 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3424 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3426 if (strEqual(artwork_new->subdir, "."))
3428 if (artwork_new->user_defined)
3430 setString(&artwork_new->identifier, "private");
3431 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3435 setString(&artwork_new->identifier, "classic");
3436 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3439 /* set to new values after changing ".sort_priority" */
3440 artwork_new->color = ARTWORKCOLOR(artwork_new);
3442 setString(&artwork_new->class_desc,
3443 getLevelClassDescription(artwork_new));
3447 setString(&artwork_new->identifier, artwork_new->subdir);
3450 setString(&artwork_new->name, artwork_new->identifier);
3451 setString(&artwork_new->name_sorting, artwork_new->name);
3455 DrawInitText(artwork_new->name, 150, FC_YELLOW);
3458 pushTreeInfo(node_first, artwork_new);
3460 freeSetupFileHash(setup_file_hash);
3462 free(directory_path);
3468 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3469 TreeInfo *node_parent,
3470 char *base_directory, int type)
3473 struct dirent *dir_entry;
3474 boolean valid_entry_found = FALSE;
3476 if ((dir = opendir(base_directory)) == NULL)
3478 /* display error if directory is main "options.graphics_directory" etc. */
3479 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3480 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3485 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
3487 struct stat file_status;
3488 char *directory_name = dir_entry->d_name;
3489 char *directory_path = getPath2(base_directory, directory_name);
3491 /* skip directory entries for current and parent directory */
3492 if (strEqual(directory_name, ".") ||
3493 strEqual(directory_name, ".."))
3495 free(directory_path);
3499 /* skip directory entries which are not a directory or are not accessible */
3500 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
3501 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
3503 free(directory_path);
3507 free(directory_path);
3509 /* check if this directory contains artwork with or without config file */
3510 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3512 directory_name, type);
3517 /* check if this directory directly contains artwork itself */
3518 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3519 base_directory, ".",
3521 if (!valid_entry_found)
3522 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3526 static TreeInfo *getDummyArtworkInfo(int type)
3528 /* this is only needed when there is completely no artwork available */
3529 TreeInfo *artwork_new = newTreeInfo();
3531 setTreeInfoToDefaults(artwork_new, type);
3533 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3534 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3535 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3537 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3538 setString(&artwork_new->name, UNDEFINED_FILENAME);
3539 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3544 void LoadArtworkInfo()
3546 LoadArtworkInfoCache();
3548 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3550 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3551 options.graphics_directory,
3552 TREE_TYPE_GRAPHICS_DIR);
3553 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3554 getUserGraphicsDir(),
3555 TREE_TYPE_GRAPHICS_DIR);
3557 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3558 options.sounds_directory,
3559 TREE_TYPE_SOUNDS_DIR);
3560 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3562 TREE_TYPE_SOUNDS_DIR);
3564 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3565 options.music_directory,
3566 TREE_TYPE_MUSIC_DIR);
3567 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3569 TREE_TYPE_MUSIC_DIR);
3571 if (artwork.gfx_first == NULL)
3572 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3573 if (artwork.snd_first == NULL)
3574 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3575 if (artwork.mus_first == NULL)
3576 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3578 /* before sorting, the first entries will be from the user directory */
3579 artwork.gfx_current =
3580 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3581 if (artwork.gfx_current == NULL)
3582 artwork.gfx_current =
3583 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3584 if (artwork.gfx_current == NULL)
3585 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3587 artwork.snd_current =
3588 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3589 if (artwork.snd_current == NULL)
3590 artwork.snd_current =
3591 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3592 if (artwork.snd_current == NULL)
3593 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3595 artwork.mus_current =
3596 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3597 if (artwork.mus_current == NULL)
3598 artwork.mus_current =
3599 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3600 if (artwork.mus_current == NULL)
3601 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3603 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3604 artwork.snd_current_identifier = artwork.snd_current->identifier;
3605 artwork.mus_current_identifier = artwork.mus_current->identifier;
3608 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3609 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3610 printf("music set == %s\n\n", artwork.mus_current_identifier);
3613 sortTreeInfo(&artwork.gfx_first);
3614 sortTreeInfo(&artwork.snd_first);
3615 sortTreeInfo(&artwork.mus_first);
3618 dumpTreeInfo(artwork.gfx_first, 0);
3619 dumpTreeInfo(artwork.snd_first, 0);
3620 dumpTreeInfo(artwork.mus_first, 0);
3624 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3625 LevelDirTree *level_node)
3628 static unsigned long progress_delay = 0;
3629 unsigned long progress_delay_value = 100; /* (in milliseconds) */
3631 int type = (*artwork_node)->type;
3633 /* recursively check all level directories for artwork sub-directories */
3637 /* check all tree entries for artwork, but skip parent link entries */
3638 if (!level_node->parent_link)
3640 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3641 boolean cached = (artwork_new != NULL);
3645 pushTreeInfo(artwork_node, artwork_new);
3649 TreeInfo *topnode_last = *artwork_node;
3650 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3651 ARTWORK_DIRECTORY(type));
3653 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3655 if (topnode_last != *artwork_node) /* check for newly added node */
3657 artwork_new = *artwork_node;
3659 setString(&artwork_new->identifier, level_node->subdir);
3660 setString(&artwork_new->name, level_node->name);
3661 setString(&artwork_new->name_sorting, level_node->name_sorting);
3663 artwork_new->sort_priority = level_node->sort_priority;
3664 artwork_new->color = LEVELCOLOR(artwork_new);
3670 /* insert artwork info (from old cache or filesystem) into new cache */
3671 if (artwork_new != NULL)
3672 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3676 DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3677 level_node->level_group);
3679 if (level_node->level_group ||
3680 DelayReached(&progress_delay, progress_delay_value))
3681 DrawInitText(level_node->name, 150, FC_YELLOW);
3684 if (level_node->node_group != NULL)
3685 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3687 level_node = level_node->next;
3691 void LoadLevelArtworkInfo()
3693 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3695 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3696 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3697 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3699 SaveArtworkInfoCache();
3701 /* needed for reloading level artwork not known at ealier stage */
3703 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3705 artwork.gfx_current =
3706 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3707 if (artwork.gfx_current == NULL)
3708 artwork.gfx_current =
3709 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3710 if (artwork.gfx_current == NULL)
3711 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3714 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3716 artwork.snd_current =
3717 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3718 if (artwork.snd_current == NULL)
3719 artwork.snd_current =
3720 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3721 if (artwork.snd_current == NULL)
3722 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3725 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3727 artwork.mus_current =
3728 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3729 if (artwork.mus_current == NULL)
3730 artwork.mus_current =
3731 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3732 if (artwork.mus_current == NULL)
3733 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3736 sortTreeInfo(&artwork.gfx_first);
3737 sortTreeInfo(&artwork.snd_first);
3738 sortTreeInfo(&artwork.mus_first);
3741 dumpTreeInfo(artwork.gfx_first, 0);
3742 dumpTreeInfo(artwork.snd_first, 0);
3743 dumpTreeInfo(artwork.mus_first, 0);
3747 static void SaveUserLevelInfo()
3749 LevelDirTree *level_info;
3754 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3756 if (!(file = fopen(filename, MODE_WRITE)))
3758 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3763 level_info = newTreeInfo();
3765 /* always start with reliable default values */
3766 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3768 setString(&level_info->name, getLoginName());
3769 setString(&level_info->author, getRealName());
3770 level_info->levels = 100;
3771 level_info->first_level = 1;
3773 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3775 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3776 getCookie("LEVELINFO")));
3779 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3781 if (i == LEVELINFO_TOKEN_NAME ||
3782 i == LEVELINFO_TOKEN_AUTHOR ||
3783 i == LEVELINFO_TOKEN_LEVELS ||
3784 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3785 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3787 /* just to make things nicer :) */
3788 if (i == LEVELINFO_TOKEN_AUTHOR)
3789 fprintf(file, "\n");
3792 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3796 SetFilePermissions(filename, PERMS_PRIVATE);
3798 freeTreeInfo(level_info);
3802 char *getSetupValue(int type, void *value)
3804 static char value_string[MAX_LINE_LEN];
3812 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3816 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3820 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3821 *(int *)value == FALSE ? "off" : "on"));
3825 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3828 case TYPE_YES_NO_AUTO:
3829 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3830 *(int *)value == FALSE ? "no" : "yes"));
3834 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3838 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3842 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3846 sprintf(value_string, "%d", *(int *)value);
3850 if (*(char **)value == NULL)
3853 strcpy(value_string, *(char **)value);
3857 value_string[0] = '\0';
3861 if (type & TYPE_GHOSTED)
3862 strcpy(value_string, "n/a");
3864 return value_string;
3867 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3871 static char token_string[MAX_LINE_LEN];
3872 int token_type = token_info[token_nr].type;
3873 void *setup_value = token_info[token_nr].value;
3874 char *token_text = token_info[token_nr].text;
3875 char *value_string = getSetupValue(token_type, setup_value);
3877 /* build complete token string */
3878 sprintf(token_string, "%s%s", prefix, token_text);
3880 /* build setup entry line */
3881 line = getFormattedSetupEntry(token_string, value_string);
3883 if (token_type == TYPE_KEY_X11)
3885 Key key = *(Key *)setup_value;
3886 char *keyname = getKeyNameFromKey(key);
3888 /* add comment, if useful */
3889 if (!strEqual(keyname, "(undefined)") &&
3890 !strEqual(keyname, "(unknown)"))
3892 /* add at least one whitespace */
3894 for (i = strlen(line); i < token_comment_position; i++)
3898 strcat(line, keyname);
3905 void LoadLevelSetup_LastSeries()
3907 /* ----------------------------------------------------------------------- */
3908 /* ~/.<program>/levelsetup.conf */
3909 /* ----------------------------------------------------------------------- */
3911 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3912 SetupFileHash *level_setup_hash = NULL;
3914 /* always start with reliable default values */
3915 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3917 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3918 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3920 if (leveldir_current == NULL)
3921 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3924 if ((level_setup_hash = loadSetupFileHash(filename)))
3926 char *last_level_series =
3927 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3929 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3931 if (leveldir_current == NULL)
3932 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3934 checkSetupFileHashIdentifier(level_setup_hash, filename,
3935 getCookie("LEVELSETUP"));
3937 freeSetupFileHash(level_setup_hash);
3940 Error(ERR_WARN, "using default setup values");
3945 void SaveLevelSetup_LastSeries()
3947 /* ----------------------------------------------------------------------- */
3948 /* ~/.<program>/levelsetup.conf */
3949 /* ----------------------------------------------------------------------- */
3951 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3952 char *level_subdir = leveldir_current->subdir;
3955 InitUserDataDirectory();
3957 if (!(file = fopen(filename, MODE_WRITE)))
3959 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3964 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3965 getCookie("LEVELSETUP")));
3966 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3971 SetFilePermissions(filename, PERMS_PRIVATE);
3976 static void checkSeriesInfo()
3978 static char *level_directory = NULL;
3980 struct dirent *dir_entry;
3982 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3984 level_directory = getPath2((leveldir_current->in_user_dir ?
3985 getUserLevelDir(NULL) :
3986 options.level_directory),
3987 leveldir_current->fullpath);
3989 if ((dir = opendir(level_directory)) == NULL)
3991 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3995 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
3997 if (strlen(dir_entry->d_name) > 4 &&
3998 dir_entry->d_name[3] == '.' &&
3999 strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4001 char levelnum_str[4];
4004 strncpy(levelnum_str, dir_entry->d_name, 3);
4005 levelnum_str[3] = '\0';
4007 levelnum_value = atoi(levelnum_str);
4010 if (levelnum_value < leveldir_current->first_level)
4012 Error(ERR_WARN, "additional level %d found", levelnum_value);
4013 leveldir_current->first_level = levelnum_value;
4015 else if (levelnum_value > leveldir_current->last_level)
4017 Error(ERR_WARN, "additional level %d found", levelnum_value);
4018 leveldir_current->last_level = levelnum_value;
4027 void LoadLevelSetup_SeriesInfo()
4030 SetupFileHash *level_setup_hash = NULL;
4031 char *level_subdir = leveldir_current->subdir;
4033 /* always start with reliable default values */
4034 level_nr = leveldir_current->first_level;
4036 checkSeriesInfo(leveldir_current);
4038 /* ----------------------------------------------------------------------- */
4039 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4040 /* ----------------------------------------------------------------------- */
4042 level_subdir = leveldir_current->subdir;
4044 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4046 if ((level_setup_hash = loadSetupFileHash(filename)))
4050 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4054 level_nr = atoi(token_value);
4056 if (level_nr < leveldir_current->first_level)
4057 level_nr = leveldir_current->first_level;
4058 if (level_nr > leveldir_current->last_level)
4059 level_nr = leveldir_current->last_level;
4062 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4066 int level_nr = atoi(token_value);
4068 if (level_nr < leveldir_current->first_level)
4069 level_nr = leveldir_current->first_level;
4070 if (level_nr > leveldir_current->last_level + 1)
4071 level_nr = leveldir_current->last_level;
4073 if (leveldir_current->user_defined || !leveldir_current->handicap)
4074 level_nr = leveldir_current->last_level;
4076 leveldir_current->handicap_level = level_nr;
4079 checkSetupFileHashIdentifier(level_setup_hash, filename,
4080 getCookie("LEVELSETUP"));
4082 freeSetupFileHash(level_setup_hash);
4085 Error(ERR_WARN, "using default setup values");
4090 void SaveLevelSetup_SeriesInfo()
4093 char *level_subdir = leveldir_current->subdir;
4094 char *level_nr_str = int2str(level_nr, 0);
4095 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4098 /* ----------------------------------------------------------------------- */
4099 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4100 /* ----------------------------------------------------------------------- */
4102 InitLevelSetupDirectory(level_subdir);
4104 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4106 if (!(file = fopen(filename, MODE_WRITE)))
4108 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4113 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4114 getCookie("LEVELSETUP")));
4115 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4117 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4118 handicap_level_str));
4122 SetFilePermissions(filename, PERMS_PRIVATE);