1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
33 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 char *data_dir = getCommonDataDir();
137 char *score_subdir = SCORES_DIRECTORY;
139 checked_free(score_dir);
141 if (level_subdir != NULL)
142 score_dir = getPath3(data_dir, score_subdir, level_subdir);
144 score_dir = getPath2(data_dir, score_subdir);
149 static char *getLevelSetupDir(char *level_subdir)
151 static char *levelsetup_dir = NULL;
152 char *data_dir = getUserGameDataDir();
153 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
155 checked_free(levelsetup_dir);
157 if (level_subdir != NULL)
158 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
160 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
162 return levelsetup_dir;
165 static char *getCacheDir()
167 static char *cache_dir = NULL;
169 if (cache_dir == NULL)
170 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
175 static char *getLevelDirFromTreeInfo(TreeInfo *node)
177 static char *level_dir = NULL;
180 return options.level_directory;
182 checked_free(level_dir);
184 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
185 options.level_directory), node->fullpath);
190 char *getCurrentLevelDir()
192 return getLevelDirFromTreeInfo(leveldir_current);
195 static char *getTapeDir(char *level_subdir)
197 static char *tape_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *tape_subdir = TAPES_DIRECTORY;
201 checked_free(tape_dir);
203 if (level_subdir != NULL)
204 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
206 tape_dir = getPath2(data_dir, tape_subdir);
211 static char *getSolutionTapeDir()
213 static char *tape_dir = NULL;
214 char *data_dir = getCurrentLevelDir();
215 char *tape_subdir = TAPES_DIRECTORY;
217 checked_free(tape_dir);
219 tape_dir = getPath2(data_dir, tape_subdir);
224 static char *getDefaultGraphicsDir(char *graphics_subdir)
226 static char *graphics_dir = NULL;
228 if (graphics_subdir == NULL)
229 return options.graphics_directory;
231 checked_free(graphics_dir);
233 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
238 static char *getDefaultSoundsDir(char *sounds_subdir)
240 static char *sounds_dir = NULL;
242 if (sounds_subdir == NULL)
243 return options.sounds_directory;
245 checked_free(sounds_dir);
247 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
252 static char *getDefaultMusicDir(char *music_subdir)
254 static char *music_dir = NULL;
256 if (music_subdir == NULL)
257 return options.music_directory;
259 checked_free(music_dir);
261 music_dir = getPath2(options.music_directory, music_subdir);
266 static char *getClassicArtworkSet(int type)
268 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
269 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
270 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
273 static char *getClassicArtworkDir(int type)
275 return (type == TREE_TYPE_GRAPHICS_DIR ?
276 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_SOUNDS_DIR ?
278 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
279 type == TREE_TYPE_MUSIC_DIR ?
280 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
283 static char *getUserGraphicsDir()
285 static char *usergraphics_dir = NULL;
287 if (usergraphics_dir == NULL)
288 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
290 return usergraphics_dir;
293 static char *getUserSoundsDir()
295 static char *usersounds_dir = NULL;
297 if (usersounds_dir == NULL)
298 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
300 return usersounds_dir;
303 static char *getUserMusicDir()
305 static char *usermusic_dir = NULL;
307 if (usermusic_dir == NULL)
308 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
310 return usermusic_dir;
313 static char *getSetupArtworkDir(TreeInfo *ti)
315 static char *artwork_dir = NULL;
320 checked_free(artwork_dir);
322 artwork_dir = getPath2(ti->basepath, ti->fullpath);
327 char *setLevelArtworkDir(TreeInfo *ti)
329 char **artwork_path_ptr, **artwork_set_ptr;
330 TreeInfo *level_artwork;
332 if (ti == NULL || leveldir_current == NULL)
335 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
336 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
338 checked_free(*artwork_path_ptr);
340 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
342 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
347 No (or non-existing) artwork configured in "levelinfo.conf". This would
348 normally result in using the artwork configured in the setup menu. But
349 if an artwork subdirectory exists (which might contain custom artwork
350 or an artwork configuration file), this level artwork must be treated
351 as relative to the default "classic" artwork, not to the artwork that
352 is currently configured in the setup menu.
354 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
355 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
356 the real "classic" artwork from the original R'n'D (like "gfx_classic").
359 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
361 checked_free(*artwork_set_ptr);
363 if (directoryExists(dir))
365 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
366 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
370 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
371 *artwork_set_ptr = NULL;
377 return *artwork_set_ptr;
380 inline static char *getLevelArtworkSet(int type)
382 if (leveldir_current == NULL)
385 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
388 inline static char *getLevelArtworkDir(int type)
390 if (leveldir_current == NULL)
391 return UNDEFINED_FILENAME;
393 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
396 char *getProgramConfigFilename(char *command_filename_ptr)
398 char *command_filename = getStringCopy(command_filename_ptr);
400 // strip trailing executable suffix from command filename
401 if (strSuffix(command_filename, ".exe"))
402 command_filename[strlen(command_filename) - 4] = '\0';
404 return getStringCat2(command_filename, ".conf");
407 char *getTapeFilename(int nr)
409 static char *filename = NULL;
410 char basename[MAX_FILENAME_LEN];
412 checked_free(filename);
414 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
415 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
420 char *getSolutionTapeFilename(int nr)
422 static char *filename = NULL;
423 char basename[MAX_FILENAME_LEN];
425 checked_free(filename);
427 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
428 filename = getPath2(getSolutionTapeDir(), basename);
430 if (!fileExists(filename))
432 static char *filename_sln = NULL;
434 checked_free(filename_sln);
436 sprintf(basename, "%03d.sln", nr);
437 filename_sln = getPath2(getSolutionTapeDir(), basename);
439 if (fileExists(filename_sln))
446 char *getScoreFilename(int nr)
448 static char *filename = NULL;
449 char basename[MAX_FILENAME_LEN];
451 checked_free(filename);
453 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
454 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
459 char *getSetupFilename()
461 static char *filename = NULL;
463 checked_free(filename);
465 filename = getPath2(getSetupDir(), SETUP_FILENAME);
470 char *getDefaultSetupFilename()
472 return program.config_filename;
475 char *getEditorSetupFilename()
477 static char *filename = NULL;
479 checked_free(filename);
480 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
482 if (fileExists(filename))
485 checked_free(filename);
486 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
491 char *getHelpAnimFilename()
493 static char *filename = NULL;
495 checked_free(filename);
497 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
502 char *getHelpTextFilename()
504 static char *filename = NULL;
506 checked_free(filename);
508 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
513 char *getLevelSetInfoFilename()
515 static char *filename = NULL;
530 for (i = 0; basenames[i] != NULL; i++)
532 checked_free(filename);
533 filename = getPath2(getCurrentLevelDir(), basenames[i]);
535 if (fileExists(filename))
542 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
544 static char basename[32];
546 sprintf(basename, "%s_%d.txt",
547 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
552 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
554 static char *filename = NULL;
556 boolean skip_setup_artwork = FALSE;
558 checked_free(filename);
560 basename = getLevelSetTitleMessageBasename(nr, initial);
562 if (!gfx.override_level_graphics)
564 /* 1st try: look for special artwork in current level series directory */
565 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
566 if (fileExists(filename))
571 /* 2nd try: look for message file in current level set directory */
572 filename = getPath2(getCurrentLevelDir(), basename);
573 if (fileExists(filename))
578 /* check if there is special artwork configured in level series config */
579 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
581 /* 3rd try: look for special artwork configured in level series config */
582 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
583 if (fileExists(filename))
588 /* take missing artwork configured in level set config from default */
589 skip_setup_artwork = TRUE;
593 if (!skip_setup_artwork)
595 /* 4th try: look for special artwork in configured artwork directory */
596 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
597 if (fileExists(filename))
603 /* 5th try: look for default artwork in new default artwork directory */
604 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
605 if (fileExists(filename))
610 /* 6th try: look for default artwork in old default artwork directory */
611 filename = getPath2(options.graphics_directory, basename);
612 if (fileExists(filename))
615 return NULL; /* cannot find specified artwork file anywhere */
618 static char *getCorrectedArtworkBasename(char *basename)
623 char *getCustomImageFilename(char *basename)
625 static char *filename = NULL;
626 boolean skip_setup_artwork = FALSE;
628 checked_free(filename);
630 basename = getCorrectedArtworkBasename(basename);
632 if (!gfx.override_level_graphics)
634 /* 1st try: look for special artwork in current level series directory */
635 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
636 if (fileExists(filename))
641 /* check if there is special artwork configured in level series config */
642 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
644 /* 2nd try: look for special artwork configured in level series config */
645 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
646 if (fileExists(filename))
651 /* take missing artwork configured in level set config from default */
652 skip_setup_artwork = TRUE;
656 if (!skip_setup_artwork)
658 /* 3rd try: look for special artwork in configured artwork directory */
659 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
660 if (fileExists(filename))
666 /* 4th try: look for default artwork in new default artwork directory */
667 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
668 if (fileExists(filename))
673 /* 5th try: look for default artwork in old default artwork directory */
674 filename = getImg2(options.graphics_directory, basename);
675 if (fileExists(filename))
678 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
683 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
686 /* 6th try: look for fallback artwork in old default artwork directory */
687 /* (needed to prevent errors when trying to access unused artwork files) */
688 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
689 if (fileExists(filename))
693 return NULL; /* cannot find specified artwork file anywhere */
696 char *getCustomSoundFilename(char *basename)
698 static char *filename = NULL;
699 boolean skip_setup_artwork = FALSE;
701 checked_free(filename);
703 basename = getCorrectedArtworkBasename(basename);
705 if (!gfx.override_level_sounds)
707 /* 1st try: look for special artwork in current level series directory */
708 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
709 if (fileExists(filename))
714 /* check if there is special artwork configured in level series config */
715 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
717 /* 2nd try: look for special artwork configured in level series config */
718 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
719 if (fileExists(filename))
724 /* take missing artwork configured in level set config from default */
725 skip_setup_artwork = TRUE;
729 if (!skip_setup_artwork)
731 /* 3rd try: look for special artwork in configured artwork directory */
732 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
733 if (fileExists(filename))
739 /* 4th try: look for default artwork in new default artwork directory */
740 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
741 if (fileExists(filename))
746 /* 5th try: look for default artwork in old default artwork directory */
747 filename = getPath2(options.sounds_directory, basename);
748 if (fileExists(filename))
751 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
756 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
759 /* 6th try: look for fallback artwork in old default artwork directory */
760 /* (needed to prevent errors when trying to access unused artwork files) */
761 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
762 if (fileExists(filename))
766 return NULL; /* cannot find specified artwork file anywhere */
769 char *getCustomMusicFilename(char *basename)
771 static char *filename = NULL;
772 boolean skip_setup_artwork = FALSE;
774 checked_free(filename);
776 basename = getCorrectedArtworkBasename(basename);
778 if (!gfx.override_level_music)
780 /* 1st try: look for special artwork in current level series directory */
781 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
782 if (fileExists(filename))
787 /* check if there is special artwork configured in level series config */
788 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
790 /* 2nd try: look for special artwork configured in level series config */
791 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
792 if (fileExists(filename))
797 /* take missing artwork configured in level set config from default */
798 skip_setup_artwork = TRUE;
802 if (!skip_setup_artwork)
804 /* 3rd try: look for special artwork in configured artwork directory */
805 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
806 if (fileExists(filename))
812 /* 4th try: look for default artwork in new default artwork directory */
813 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
814 if (fileExists(filename))
819 /* 5th try: look for default artwork in old default artwork directory */
820 filename = getPath2(options.music_directory, basename);
821 if (fileExists(filename))
824 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
829 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
832 /* 6th try: look for fallback artwork in old default artwork directory */
833 /* (needed to prevent errors when trying to access unused artwork files) */
834 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
835 if (fileExists(filename))
839 return NULL; /* cannot find specified artwork file anywhere */
842 char *getCustomArtworkFilename(char *basename, int type)
844 if (type == ARTWORK_TYPE_GRAPHICS)
845 return getCustomImageFilename(basename);
846 else if (type == ARTWORK_TYPE_SOUNDS)
847 return getCustomSoundFilename(basename);
848 else if (type == ARTWORK_TYPE_MUSIC)
849 return getCustomMusicFilename(basename);
851 return UNDEFINED_FILENAME;
854 char *getCustomArtworkConfigFilename(int type)
856 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
859 char *getCustomArtworkLevelConfigFilename(int type)
861 static char *filename = NULL;
863 checked_free(filename);
865 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
870 char *getCustomMusicDirectory(void)
872 static char *directory = NULL;
873 boolean skip_setup_artwork = FALSE;
875 checked_free(directory);
877 if (!gfx.override_level_music)
879 /* 1st try: look for special artwork in current level series directory */
880 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
881 if (directoryExists(directory))
886 /* check if there is special artwork configured in level series config */
887 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
889 /* 2nd try: look for special artwork configured in level series config */
890 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
891 if (directoryExists(directory))
896 /* take missing artwork configured in level set config from default */
897 skip_setup_artwork = TRUE;
901 if (!skip_setup_artwork)
903 /* 3rd try: look for special artwork in configured artwork directory */
904 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
905 if (directoryExists(directory))
911 /* 4th try: look for default artwork in new default artwork directory */
912 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
913 if (directoryExists(directory))
918 /* 5th try: look for default artwork in old default artwork directory */
919 directory = getStringCopy(options.music_directory);
920 if (directoryExists(directory))
923 return NULL; /* cannot find specified artwork file anywhere */
926 void InitTapeDirectory(char *level_subdir)
928 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
929 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
930 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
933 void InitScoreDirectory(char *level_subdir)
935 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
936 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
937 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
940 static void SaveUserLevelInfo();
942 void InitUserLevelDirectory(char *level_subdir)
944 if (!directoryExists(getUserLevelDir(level_subdir)))
946 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
947 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
948 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
954 void InitLevelSetupDirectory(char *level_subdir)
956 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
957 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
958 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
961 void InitCacheDirectory()
963 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
964 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
968 /* ------------------------------------------------------------------------- */
969 /* some functions to handle lists of level and artwork directories */
970 /* ------------------------------------------------------------------------- */
972 TreeInfo *newTreeInfo()
974 return checked_calloc(sizeof(TreeInfo));
977 TreeInfo *newTreeInfo_setDefaults(int type)
979 TreeInfo *ti = newTreeInfo();
981 setTreeInfoToDefaults(ti, type);
986 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
988 node_new->next = *node_first;
989 *node_first = node_new;
992 int numTreeInfo(TreeInfo *node)
1005 boolean validLevelSeries(TreeInfo *node)
1007 return (node != NULL && !node->node_group && !node->parent_link);
1010 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1015 if (node->node_group) /* enter level group (step down into tree) */
1016 return getFirstValidTreeInfoEntry(node->node_group);
1017 else if (node->parent_link) /* skip start entry of level group */
1019 if (node->next) /* get first real level series entry */
1020 return getFirstValidTreeInfoEntry(node->next);
1021 else /* leave empty level group and go on */
1022 return getFirstValidTreeInfoEntry(node->node_parent->next);
1024 else /* this seems to be a regular level series */
1028 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1033 if (node->node_parent == NULL) /* top level group */
1034 return *node->node_top;
1035 else /* sub level group */
1036 return node->node_parent->node_group;
1039 int numTreeInfoInGroup(TreeInfo *node)
1041 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1044 int posTreeInfo(TreeInfo *node)
1046 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1051 if (node_cmp == node)
1055 node_cmp = node_cmp->next;
1061 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1063 TreeInfo *node_default = node;
1075 return node_default;
1078 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1080 if (identifier == NULL)
1085 if (node->node_group)
1087 TreeInfo *node_group;
1089 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1094 else if (!node->parent_link)
1096 if (strEqual(identifier, node->identifier))
1106 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1107 TreeInfo *node, boolean skip_sets_without_levels)
1114 if (!node->parent_link && !node->level_group &&
1115 skip_sets_without_levels && node->levels == 0)
1116 return cloneTreeNode(node_top, node_parent, node->next,
1117 skip_sets_without_levels);
1119 node_new = getTreeInfoCopy(node); /* copy complete node */
1121 node_new->node_top = node_top; /* correct top node link */
1122 node_new->node_parent = node_parent; /* correct parent node link */
1124 if (node->level_group)
1125 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1126 skip_sets_without_levels);
1128 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1129 skip_sets_without_levels);
1134 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1136 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1138 *ti_new = ti_cloned;
1141 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1143 boolean settings_changed = FALSE;
1147 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1148 !strEqual(node->graphics_set, node->graphics_set_ecs))
1150 setString(&node->graphics_set, node->graphics_set_ecs);
1151 settings_changed = TRUE;
1153 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1154 !strEqual(node->graphics_set, node->graphics_set_aga))
1156 setString(&node->graphics_set, node->graphics_set_aga);
1157 settings_changed = TRUE;
1160 if (node->node_group != NULL)
1161 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1166 return settings_changed;
1169 void dumpTreeInfo(TreeInfo *node, int depth)
1173 printf("Dumping TreeInfo:\n");
1177 for (i = 0; i < (depth + 1) * 3; i++)
1180 printf("'%s' / '%s'\n", node->identifier, node->name);
1183 // use for dumping artwork info tree
1184 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1185 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1188 if (node->node_group != NULL)
1189 dumpTreeInfo(node->node_group, depth + 1);
1195 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1196 int (*compare_function)(const void *,
1199 int num_nodes = numTreeInfo(*node_first);
1200 TreeInfo **sort_array;
1201 TreeInfo *node = *node_first;
1207 /* allocate array for sorting structure pointers */
1208 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1210 /* writing structure pointers to sorting array */
1211 while (i < num_nodes && node) /* double boundary check... */
1213 sort_array[i] = node;
1219 /* sorting the structure pointers in the sorting array */
1220 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1223 /* update the linkage of list elements with the sorted node array */
1224 for (i = 0; i < num_nodes - 1; i++)
1225 sort_array[i]->next = sort_array[i + 1];
1226 sort_array[num_nodes - 1]->next = NULL;
1228 /* update the linkage of the main list anchor pointer */
1229 *node_first = sort_array[0];
1233 /* now recursively sort the level group structures */
1237 if (node->node_group != NULL)
1238 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1244 void sortTreeInfo(TreeInfo **node_first)
1246 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1250 /* ========================================================================= */
1251 /* some stuff from "files.c" */
1252 /* ========================================================================= */
1254 #if defined(PLATFORM_WIN32)
1256 #define S_IRGRP S_IRUSR
1259 #define S_IROTH S_IRUSR
1262 #define S_IWGRP S_IWUSR
1265 #define S_IWOTH S_IWUSR
1268 #define S_IXGRP S_IXUSR
1271 #define S_IXOTH S_IXUSR
1274 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1279 #endif /* PLATFORM_WIN32 */
1281 /* file permissions for newly written files */
1282 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1283 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1284 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1286 #define MODE_W_PRIVATE (S_IWUSR)
1287 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1288 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1290 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1291 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1293 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1294 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1298 static char *dir = NULL;
1300 #if defined(PLATFORM_WIN32)
1303 dir = checked_malloc(MAX_PATH + 1);
1305 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1308 #elif defined(PLATFORM_UNIX)
1311 if ((dir = getenv("HOME")) == NULL)
1315 if ((pwd = getpwuid(getuid())) != NULL)
1316 dir = getStringCopy(pwd->pw_dir);
1328 char *getCommonDataDir(void)
1330 static char *common_data_dir = NULL;
1332 #if defined(PLATFORM_WIN32)
1333 if (common_data_dir == NULL)
1335 char *dir = checked_malloc(MAX_PATH + 1);
1337 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1338 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1339 common_data_dir = getPath2(dir, program.userdata_subdir);
1341 common_data_dir = options.rw_base_directory;
1344 if (common_data_dir == NULL)
1345 common_data_dir = options.rw_base_directory;
1348 return common_data_dir;
1351 char *getPersonalDataDir(void)
1353 static char *personal_data_dir = NULL;
1355 #if defined(PLATFORM_MACOSX)
1356 if (personal_data_dir == NULL)
1357 personal_data_dir = getPath2(getHomeDir(), "Documents");
1359 if (personal_data_dir == NULL)
1360 personal_data_dir = getHomeDir();
1363 return personal_data_dir;
1366 char *getUserGameDataDir(void)
1368 static char *user_game_data_dir = NULL;
1370 #if defined(PLATFORM_ANDROID)
1371 if (user_game_data_dir == NULL)
1372 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1374 if (user_game_data_dir == NULL)
1375 user_game_data_dir = getPath2(getPersonalDataDir(),
1376 program.userdata_subdir);
1379 return user_game_data_dir;
1382 void updateUserGameDataDir()
1384 #if defined(PLATFORM_MACOSX)
1385 char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1386 char *userdata_dir_new = getUserGameDataDir(); /* do not free() this */
1388 /* convert old Unix style game data directory to Mac OS X style, if needed */
1389 if (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
1391 if (rename(userdata_dir_old, userdata_dir_new) != 0)
1393 Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1394 userdata_dir_old, userdata_dir_new);
1396 /* continue using Unix style data directory -- this should not happen */
1397 program.userdata_path = getPath2(getPersonalDataDir(),
1398 program.userdata_subdir_unix);
1402 free(userdata_dir_old);
1408 return getUserGameDataDir();
1411 static mode_t posix_umask(mode_t mask)
1413 #if defined(PLATFORM_UNIX)
1420 static int posix_mkdir(const char *pathname, mode_t mode)
1422 #if defined(PLATFORM_WIN32)
1423 return mkdir(pathname);
1425 return mkdir(pathname, mode);
1429 static boolean posix_process_running_setgid()
1431 #if defined(PLATFORM_UNIX)
1432 return (getgid() != getegid());
1438 void createDirectory(char *dir, char *text, int permission_class)
1440 /* leave "other" permissions in umask untouched, but ensure group parts
1441 of USERDATA_DIR_MODE are not masked */
1442 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1443 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1444 mode_t last_umask = posix_umask(0);
1445 mode_t group_umask = ~(dir_mode & S_IRWXG);
1446 int running_setgid = posix_process_running_setgid();
1448 /* if we're setgid, protect files against "other" */
1449 /* else keep umask(0) to make the dir world-writable */
1452 posix_umask(last_umask & group_umask);
1454 dir_mode |= MODE_W_ALL;
1456 if (!directoryExists(dir))
1457 if (posix_mkdir(dir, dir_mode) != 0)
1458 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1459 text, dir, strerror(errno));
1461 if (permission_class == PERMS_PUBLIC && !running_setgid)
1462 chmod(dir, dir_mode);
1464 posix_umask(last_umask); /* restore previous umask */
1467 void InitUserDataDirectory()
1469 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1472 void SetFilePermissions(char *filename, int permission_class)
1474 int running_setgid = posix_process_running_setgid();
1475 int perms = (permission_class == PERMS_PRIVATE ?
1476 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1478 if (permission_class == PERMS_PUBLIC && !running_setgid)
1479 perms |= MODE_W_ALL;
1481 chmod(filename, perms);
1484 char *getCookie(char *file_type)
1486 static char cookie[MAX_COOKIE_LEN + 1];
1488 if (strlen(program.cookie_prefix) + 1 +
1489 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1490 return "[COOKIE ERROR]"; /* should never happen */
1492 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1493 program.cookie_prefix, file_type,
1494 program.version_major, program.version_minor);
1499 void fprintFileHeader(FILE *file, char *basename)
1501 char *prefix = "# ";
1504 fprintf_line_with_prefix(file, prefix, sep1, 77);
1505 fprintf(file, "%s%s\n", prefix, basename);
1506 fprintf_line_with_prefix(file, prefix, sep1, 77);
1507 fprintf(file, "\n");
1510 int getFileVersionFromCookieString(const char *cookie)
1512 const char *ptr_cookie1, *ptr_cookie2;
1513 const char *pattern1 = "_FILE_VERSION_";
1514 const char *pattern2 = "?.?";
1515 const int len_cookie = strlen(cookie);
1516 const int len_pattern1 = strlen(pattern1);
1517 const int len_pattern2 = strlen(pattern2);
1518 const int len_pattern = len_pattern1 + len_pattern2;
1519 int version_major, version_minor;
1521 if (len_cookie <= len_pattern)
1524 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1525 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1527 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1530 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1531 ptr_cookie2[1] != '.' ||
1532 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1535 version_major = ptr_cookie2[0] - '0';
1536 version_minor = ptr_cookie2[2] - '0';
1538 return VERSION_IDENT(version_major, version_minor, 0, 0);
1541 boolean checkCookieString(const char *cookie, const char *template)
1543 const char *pattern = "_FILE_VERSION_?.?";
1544 const int len_cookie = strlen(cookie);
1545 const int len_template = strlen(template);
1546 const int len_pattern = strlen(pattern);
1548 if (len_cookie != len_template)
1551 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1558 /* ------------------------------------------------------------------------- */
1559 /* setup file list and hash handling functions */
1560 /* ------------------------------------------------------------------------- */
1562 char *getFormattedSetupEntry(char *token, char *value)
1565 static char entry[MAX_LINE_LEN];
1567 /* if value is an empty string, just return token without value */
1571 /* start with the token and some spaces to format output line */
1572 sprintf(entry, "%s:", token);
1573 for (i = strlen(entry); i < token_value_position; i++)
1576 /* continue with the token's value */
1577 strcat(entry, value);
1582 SetupFileList *newSetupFileList(char *token, char *value)
1584 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1586 new->token = getStringCopy(token);
1587 new->value = getStringCopy(value);
1594 void freeSetupFileList(SetupFileList *list)
1599 checked_free(list->token);
1600 checked_free(list->value);
1603 freeSetupFileList(list->next);
1608 char *getListEntry(SetupFileList *list, char *token)
1613 if (strEqual(list->token, token))
1616 return getListEntry(list->next, token);
1619 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1624 if (strEqual(list->token, token))
1626 checked_free(list->value);
1628 list->value = getStringCopy(value);
1632 else if (list->next == NULL)
1633 return (list->next = newSetupFileList(token, value));
1635 return setListEntry(list->next, token, value);
1638 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1643 if (list->next == NULL)
1644 return (list->next = newSetupFileList(token, value));
1646 return addListEntry(list->next, token, value);
1649 #if ENABLE_UNUSED_CODE
1651 static void printSetupFileList(SetupFileList *list)
1656 printf("token: '%s'\n", list->token);
1657 printf("value: '%s'\n", list->value);
1659 printSetupFileList(list->next);
1665 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1666 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1667 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1668 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1670 #define insert_hash_entry hashtable_insert
1671 #define search_hash_entry hashtable_search
1672 #define change_hash_entry hashtable_change
1673 #define remove_hash_entry hashtable_remove
1676 unsigned int get_hash_from_key(void *key)
1681 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1682 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1683 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1684 it works better than many other constants, prime or not) has never been
1685 adequately explained.
1687 If you just want to have a good hash function, and cannot wait, djb2
1688 is one of the best string hash functions i know. It has excellent
1689 distribution and speed on many different sets of keys and table sizes.
1690 You are not likely to do better with one of the "well known" functions
1691 such as PJW, K&R, etc.
1693 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1696 char *str = (char *)key;
1697 unsigned int hash = 5381;
1700 while ((c = *str++))
1701 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1706 static int keys_are_equal(void *key1, void *key2)
1708 return (strEqual((char *)key1, (char *)key2));
1711 SetupFileHash *newSetupFileHash()
1713 SetupFileHash *new_hash =
1714 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1716 if (new_hash == NULL)
1717 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1722 void freeSetupFileHash(SetupFileHash *hash)
1727 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1730 char *getHashEntry(SetupFileHash *hash, char *token)
1735 return search_hash_entry(hash, token);
1738 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1745 value_copy = getStringCopy(value);
1747 /* change value; if it does not exist, insert it as new */
1748 if (!change_hash_entry(hash, token, value_copy))
1749 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1750 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1753 char *removeHashEntry(SetupFileHash *hash, char *token)
1758 return remove_hash_entry(hash, token);
1761 #if ENABLE_UNUSED_CODE
1763 static void printSetupFileHash(SetupFileHash *hash)
1765 BEGIN_HASH_ITERATION(hash, itr)
1767 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1768 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1770 END_HASH_ITERATION(hash, itr)
1775 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1776 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1777 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1779 static boolean token_value_separator_found = FALSE;
1780 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1781 static boolean token_value_separator_warning = FALSE;
1783 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1784 static boolean token_already_exists_warning = FALSE;
1787 static boolean getTokenValueFromSetupLineExt(char *line,
1788 char **token_ptr, char **value_ptr,
1789 char *filename, char *line_raw,
1791 boolean separator_required)
1793 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1794 char *token, *value, *line_ptr;
1796 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1797 if (line_raw == NULL)
1799 strncpy(line_copy, line, MAX_LINE_LEN);
1800 line_copy[MAX_LINE_LEN] = '\0';
1803 strcpy(line_raw_copy, line_copy);
1804 line_raw = line_raw_copy;
1807 /* cut trailing comment from input line */
1808 for (line_ptr = line; *line_ptr; line_ptr++)
1810 if (*line_ptr == '#')
1817 /* cut trailing whitespaces from input line */
1818 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1819 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1822 /* ignore empty lines */
1826 /* cut leading whitespaces from token */
1827 for (token = line; *token; token++)
1828 if (*token != ' ' && *token != '\t')
1831 /* start with empty value as reliable default */
1834 token_value_separator_found = FALSE;
1836 /* find end of token to determine start of value */
1837 for (line_ptr = token; *line_ptr; line_ptr++)
1839 /* first look for an explicit token/value separator, like ':' or '=' */
1840 if (*line_ptr == ':' || *line_ptr == '=')
1842 *line_ptr = '\0'; /* terminate token string */
1843 value = line_ptr + 1; /* set beginning of value */
1845 token_value_separator_found = TRUE;
1851 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1852 /* fallback: if no token/value separator found, also allow whitespaces */
1853 if (!token_value_separator_found && !separator_required)
1855 for (line_ptr = token; *line_ptr; line_ptr++)
1857 if (*line_ptr == ' ' || *line_ptr == '\t')
1859 *line_ptr = '\0'; /* terminate token string */
1860 value = line_ptr + 1; /* set beginning of value */
1862 token_value_separator_found = TRUE;
1868 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1869 if (token_value_separator_found)
1871 if (!token_value_separator_warning)
1873 Error(ERR_INFO_LINE, "-");
1875 if (filename != NULL)
1877 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1878 Error(ERR_INFO, "- config file: '%s'", filename);
1882 Error(ERR_WARN, "missing token/value separator(s):");
1885 token_value_separator_warning = TRUE;
1888 if (filename != NULL)
1889 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1891 Error(ERR_INFO, "- line: '%s'", line_raw);
1897 /* cut trailing whitespaces from token */
1898 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1899 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1902 /* cut leading whitespaces from value */
1903 for (; *value; value++)
1904 if (*value != ' ' && *value != '\t')
1913 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1915 /* while the internal (old) interface does not require a token/value
1916 separator (for downwards compatibility with existing files which
1917 don't use them), it is mandatory for the external (new) interface */
1919 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1922 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1923 boolean top_recursion_level, boolean is_hash)
1925 static SetupFileHash *include_filename_hash = NULL;
1926 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1927 char *token, *value, *line_ptr;
1928 void *insert_ptr = NULL;
1929 boolean read_continued_line = FALSE;
1931 int line_nr = 0, token_count = 0, include_count = 0;
1933 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1934 token_value_separator_warning = FALSE;
1937 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1938 token_already_exists_warning = FALSE;
1941 if (!(file = openFile(filename, MODE_READ)))
1943 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1948 /* use "insert pointer" to store list end for constant insertion complexity */
1950 insert_ptr = setup_file_data;
1952 /* on top invocation, create hash to mark included files (to prevent loops) */
1953 if (top_recursion_level)
1954 include_filename_hash = newSetupFileHash();
1956 /* mark this file as already included (to prevent including it again) */
1957 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1959 while (!checkEndOfFile(file))
1961 /* read next line of input file */
1962 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1965 /* check if line was completely read and is terminated by line break */
1966 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1969 /* cut trailing line break (this can be newline and/or carriage return) */
1970 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1971 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1974 /* copy raw input line for later use (mainly debugging output) */
1975 strcpy(line_raw, line);
1977 if (read_continued_line)
1979 /* append new line to existing line, if there is enough space */
1980 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1981 strcat(previous_line, line_ptr);
1983 strcpy(line, previous_line); /* copy storage buffer to line */
1985 read_continued_line = FALSE;
1988 /* if the last character is '\', continue at next line */
1989 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1991 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1992 strcpy(previous_line, line); /* copy line to storage buffer */
1994 read_continued_line = TRUE;
1999 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2000 line_raw, line_nr, FALSE))
2005 if (strEqual(token, "include"))
2007 if (getHashEntry(include_filename_hash, value) == NULL)
2009 char *basepath = getBasePath(filename);
2010 char *basename = getBaseName(value);
2011 char *filename_include = getPath2(basepath, basename);
2013 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2017 free(filename_include);
2023 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2030 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2032 getHashEntry((SetupFileHash *)setup_file_data, token);
2034 if (old_value != NULL)
2036 if (!token_already_exists_warning)
2038 Error(ERR_INFO_LINE, "-");
2039 Error(ERR_WARN, "duplicate token(s) found in config file:");
2040 Error(ERR_INFO, "- config file: '%s'", filename);
2042 token_already_exists_warning = TRUE;
2045 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2046 Error(ERR_INFO, " old value: '%s'", old_value);
2047 Error(ERR_INFO, " new value: '%s'", value);
2051 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2055 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2065 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2066 if (token_value_separator_warning)
2067 Error(ERR_INFO_LINE, "-");
2070 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2071 if (token_already_exists_warning)
2072 Error(ERR_INFO_LINE, "-");
2075 if (token_count == 0 && include_count == 0)
2076 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2078 if (top_recursion_level)
2079 freeSetupFileHash(include_filename_hash);
2084 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2088 if (!(file = fopen(filename, MODE_WRITE)))
2090 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2095 BEGIN_HASH_ITERATION(hash, itr)
2097 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2098 HASH_ITERATION_VALUE(itr)));
2100 END_HASH_ITERATION(hash, itr)
2105 SetupFileList *loadSetupFileList(char *filename)
2107 SetupFileList *setup_file_list = newSetupFileList("", "");
2108 SetupFileList *first_valid_list_entry;
2110 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2112 freeSetupFileList(setup_file_list);
2117 first_valid_list_entry = setup_file_list->next;
2119 /* free empty list header */
2120 setup_file_list->next = NULL;
2121 freeSetupFileList(setup_file_list);
2123 return first_valid_list_entry;
2126 SetupFileHash *loadSetupFileHash(char *filename)
2128 SetupFileHash *setup_file_hash = newSetupFileHash();
2130 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2132 freeSetupFileHash(setup_file_hash);
2137 return setup_file_hash;
2141 /* ========================================================================= */
2142 /* setup file stuff */
2143 /* ========================================================================= */
2145 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2146 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2147 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2149 /* level directory info */
2150 #define LEVELINFO_TOKEN_IDENTIFIER 0
2151 #define LEVELINFO_TOKEN_NAME 1
2152 #define LEVELINFO_TOKEN_NAME_SORTING 2
2153 #define LEVELINFO_TOKEN_AUTHOR 3
2154 #define LEVELINFO_TOKEN_YEAR 4
2155 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2156 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2157 #define LEVELINFO_TOKEN_TESTED_BY 7
2158 #define LEVELINFO_TOKEN_LEVELS 8
2159 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2160 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2161 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2162 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2163 #define LEVELINFO_TOKEN_READONLY 13
2164 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2165 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2166 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2167 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2168 #define LEVELINFO_TOKEN_MUSIC_SET 18
2169 #define LEVELINFO_TOKEN_FILENAME 19
2170 #define LEVELINFO_TOKEN_FILETYPE 20
2171 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2172 #define LEVELINFO_TOKEN_HANDICAP 22
2173 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2175 #define NUM_LEVELINFO_TOKENS 24
2177 static LevelDirTree ldi;
2179 static struct TokenInfo levelinfo_tokens[] =
2181 /* level directory info */
2182 { TYPE_STRING, &ldi.identifier, "identifier" },
2183 { TYPE_STRING, &ldi.name, "name" },
2184 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2185 { TYPE_STRING, &ldi.author, "author" },
2186 { TYPE_STRING, &ldi.year, "year" },
2187 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2188 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2189 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2190 { TYPE_INTEGER, &ldi.levels, "levels" },
2191 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2192 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2193 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2194 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2195 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2196 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2197 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2198 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2199 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2200 { TYPE_STRING, &ldi.music_set, "music_set" },
2201 { TYPE_STRING, &ldi.level_filename, "filename" },
2202 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2203 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2204 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2205 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2208 static struct TokenInfo artworkinfo_tokens[] =
2210 /* artwork directory info */
2211 { TYPE_STRING, &ldi.identifier, "identifier" },
2212 { TYPE_STRING, &ldi.subdir, "subdir" },
2213 { TYPE_STRING, &ldi.name, "name" },
2214 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2215 { TYPE_STRING, &ldi.author, "author" },
2216 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2217 { TYPE_STRING, &ldi.basepath, "basepath" },
2218 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2219 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2220 { TYPE_INTEGER, &ldi.color, "color" },
2221 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2226 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2230 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2231 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2232 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2233 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2236 ti->node_parent = NULL;
2237 ti->node_group = NULL;
2244 ti->fullpath = NULL;
2245 ti->basepath = NULL;
2246 ti->identifier = NULL;
2247 ti->name = getStringCopy(ANONYMOUS_NAME);
2248 ti->name_sorting = NULL;
2249 ti->author = getStringCopy(ANONYMOUS_NAME);
2252 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2253 ti->latest_engine = FALSE; /* default: get from level */
2254 ti->parent_link = FALSE;
2255 ti->in_user_dir = FALSE;
2256 ti->user_defined = FALSE;
2258 ti->class_desc = NULL;
2260 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2262 if (ti->type == TREE_TYPE_LEVEL_DIR)
2264 ti->imported_from = NULL;
2265 ti->imported_by = NULL;
2266 ti->tested_by = NULL;
2268 ti->graphics_set_ecs = NULL;
2269 ti->graphics_set_aga = NULL;
2270 ti->graphics_set = NULL;
2271 ti->sounds_set = NULL;
2272 ti->music_set = NULL;
2273 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2274 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2275 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2277 ti->level_filename = NULL;
2278 ti->level_filetype = NULL;
2280 ti->special_flags = NULL;
2283 ti->first_level = 0;
2285 ti->level_group = FALSE;
2286 ti->handicap_level = 0;
2287 ti->readonly = TRUE;
2288 ti->handicap = TRUE;
2289 ti->skip_levels = FALSE;
2293 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2297 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2299 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2304 /* copy all values from the parent structure */
2306 ti->type = parent->type;
2308 ti->node_top = parent->node_top;
2309 ti->node_parent = parent;
2310 ti->node_group = NULL;
2317 ti->fullpath = NULL;
2318 ti->basepath = NULL;
2319 ti->identifier = NULL;
2320 ti->name = getStringCopy(ANONYMOUS_NAME);
2321 ti->name_sorting = NULL;
2322 ti->author = getStringCopy(parent->author);
2323 ti->year = getStringCopy(parent->year);
2325 ti->sort_priority = parent->sort_priority;
2326 ti->latest_engine = parent->latest_engine;
2327 ti->parent_link = FALSE;
2328 ti->in_user_dir = parent->in_user_dir;
2329 ti->user_defined = parent->user_defined;
2330 ti->color = parent->color;
2331 ti->class_desc = getStringCopy(parent->class_desc);
2333 ti->infotext = getStringCopy(parent->infotext);
2335 if (ti->type == TREE_TYPE_LEVEL_DIR)
2337 ti->imported_from = getStringCopy(parent->imported_from);
2338 ti->imported_by = getStringCopy(parent->imported_by);
2339 ti->tested_by = getStringCopy(parent->tested_by);
2341 ti->graphics_set_ecs = NULL;
2342 ti->graphics_set_aga = NULL;
2343 ti->graphics_set = NULL;
2344 ti->sounds_set = NULL;
2345 ti->music_set = NULL;
2346 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2347 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2348 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2350 ti->level_filename = NULL;
2351 ti->level_filetype = NULL;
2353 ti->special_flags = getStringCopy(parent->special_flags);
2356 ti->first_level = 0;
2358 ti->level_group = FALSE;
2359 ti->handicap_level = 0;
2360 ti->readonly = parent->readonly;
2361 ti->handicap = TRUE;
2362 ti->skip_levels = FALSE;
2366 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2368 TreeInfo *ti_copy = newTreeInfo();
2370 /* copy all values from the original structure */
2372 ti_copy->type = ti->type;
2374 ti_copy->node_top = ti->node_top;
2375 ti_copy->node_parent = ti->node_parent;
2376 ti_copy->node_group = ti->node_group;
2377 ti_copy->next = ti->next;
2379 ti_copy->cl_first = ti->cl_first;
2380 ti_copy->cl_cursor = ti->cl_cursor;
2382 ti_copy->subdir = getStringCopy(ti->subdir);
2383 ti_copy->fullpath = getStringCopy(ti->fullpath);
2384 ti_copy->basepath = getStringCopy(ti->basepath);
2385 ti_copy->identifier = getStringCopy(ti->identifier);
2386 ti_copy->name = getStringCopy(ti->name);
2387 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2388 ti_copy->author = getStringCopy(ti->author);
2389 ti_copy->year = getStringCopy(ti->year);
2390 ti_copy->imported_from = getStringCopy(ti->imported_from);
2391 ti_copy->imported_by = getStringCopy(ti->imported_by);
2392 ti_copy->tested_by = getStringCopy(ti->tested_by);
2394 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2395 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2396 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2397 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2398 ti_copy->music_set = getStringCopy(ti->music_set);
2399 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2400 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2401 ti_copy->music_path = getStringCopy(ti->music_path);
2403 ti_copy->level_filename = getStringCopy(ti->level_filename);
2404 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2406 ti_copy->special_flags = getStringCopy(ti->special_flags);
2408 ti_copy->levels = ti->levels;
2409 ti_copy->first_level = ti->first_level;
2410 ti_copy->last_level = ti->last_level;
2411 ti_copy->sort_priority = ti->sort_priority;
2413 ti_copy->latest_engine = ti->latest_engine;
2415 ti_copy->level_group = ti->level_group;
2416 ti_copy->parent_link = ti->parent_link;
2417 ti_copy->in_user_dir = ti->in_user_dir;
2418 ti_copy->user_defined = ti->user_defined;
2419 ti_copy->readonly = ti->readonly;
2420 ti_copy->handicap = ti->handicap;
2421 ti_copy->skip_levels = ti->skip_levels;
2423 ti_copy->color = ti->color;
2424 ti_copy->class_desc = getStringCopy(ti->class_desc);
2425 ti_copy->handicap_level = ti->handicap_level;
2427 ti_copy->infotext = getStringCopy(ti->infotext);
2432 void freeTreeInfo(TreeInfo *ti)
2437 checked_free(ti->subdir);
2438 checked_free(ti->fullpath);
2439 checked_free(ti->basepath);
2440 checked_free(ti->identifier);
2442 checked_free(ti->name);
2443 checked_free(ti->name_sorting);
2444 checked_free(ti->author);
2445 checked_free(ti->year);
2447 checked_free(ti->class_desc);
2449 checked_free(ti->infotext);
2451 if (ti->type == TREE_TYPE_LEVEL_DIR)
2453 checked_free(ti->imported_from);
2454 checked_free(ti->imported_by);
2455 checked_free(ti->tested_by);
2457 checked_free(ti->graphics_set_ecs);
2458 checked_free(ti->graphics_set_aga);
2459 checked_free(ti->graphics_set);
2460 checked_free(ti->sounds_set);
2461 checked_free(ti->music_set);
2463 checked_free(ti->graphics_path);
2464 checked_free(ti->sounds_path);
2465 checked_free(ti->music_path);
2467 checked_free(ti->level_filename);
2468 checked_free(ti->level_filetype);
2470 checked_free(ti->special_flags);
2473 // recursively free child node
2475 freeTreeInfo(ti->node_group);
2477 // recursively free next node
2479 freeTreeInfo(ti->next);
2484 void setSetupInfo(struct TokenInfo *token_info,
2485 int token_nr, char *token_value)
2487 int token_type = token_info[token_nr].type;
2488 void *setup_value = token_info[token_nr].value;
2490 if (token_value == NULL)
2493 /* set setup field to corresponding token value */
2498 *(boolean *)setup_value = get_boolean_from_string(token_value);
2502 *(int *)setup_value = get_switch3_from_string(token_value);
2506 *(Key *)setup_value = getKeyFromKeyName(token_value);
2510 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2514 *(int *)setup_value = get_integer_from_string(token_value);
2518 checked_free(*(char **)setup_value);
2519 *(char **)setup_value = getStringCopy(token_value);
2527 static int compareTreeInfoEntries(const void *object1, const void *object2)
2529 const TreeInfo *entry1 = *((TreeInfo **)object1);
2530 const TreeInfo *entry2 = *((TreeInfo **)object2);
2531 int class_sorting1 = 0, class_sorting2 = 0;
2534 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2536 class_sorting1 = LEVELSORTING(entry1);
2537 class_sorting2 = LEVELSORTING(entry2);
2539 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2540 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2541 entry1->type == TREE_TYPE_MUSIC_DIR)
2543 class_sorting1 = ARTWORKSORTING(entry1);
2544 class_sorting2 = ARTWORKSORTING(entry2);
2547 if (entry1->parent_link || entry2->parent_link)
2548 compare_result = (entry1->parent_link ? -1 : +1);
2549 else if (entry1->sort_priority == entry2->sort_priority)
2551 char *name1 = getStringToLower(entry1->name_sorting);
2552 char *name2 = getStringToLower(entry2->name_sorting);
2554 compare_result = strcmp(name1, name2);
2559 else if (class_sorting1 == class_sorting2)
2560 compare_result = entry1->sort_priority - entry2->sort_priority;
2562 compare_result = class_sorting1 - class_sorting2;
2564 return compare_result;
2567 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2571 if (node_parent == NULL)
2574 ti_new = newTreeInfo();
2575 setTreeInfoToDefaults(ti_new, node_parent->type);
2577 ti_new->node_parent = node_parent;
2578 ti_new->parent_link = TRUE;
2580 setString(&ti_new->identifier, node_parent->identifier);
2581 setString(&ti_new->name, ".. (parent directory)");
2582 setString(&ti_new->name_sorting, ti_new->name);
2584 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2585 setString(&ti_new->fullpath, node_parent->fullpath);
2587 ti_new->sort_priority = node_parent->sort_priority;
2588 ti_new->latest_engine = node_parent->latest_engine;
2590 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2592 pushTreeInfo(&node_parent->node_group, ti_new);
2597 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2599 TreeInfo *ti_new, *ti_new2;
2601 if (node_first == NULL)
2604 ti_new = newTreeInfo();
2605 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2607 ti_new->node_parent = NULL;
2608 ti_new->parent_link = FALSE;
2610 setString(&ti_new->identifier, node_first->identifier);
2611 setString(&ti_new->name, "level sets");
2612 setString(&ti_new->name_sorting, ti_new->name);
2614 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2615 setString(&ti_new->fullpath, node_first->fullpath);
2617 ti_new->sort_priority = node_first->sort_priority;;
2618 ti_new->latest_engine = node_first->latest_engine;
2620 setString(&ti_new->class_desc, "level sets");
2622 ti_new->node_group = node_first;
2623 ti_new->level_group = TRUE;
2625 ti_new2 = createParentTreeInfoNode(ti_new);
2627 setString(&ti_new2->name, ".. (main menu)");
2628 setString(&ti_new2->name_sorting, ti_new2->name);
2634 /* -------------------------------------------------------------------------- */
2635 /* functions for handling level and custom artwork info cache */
2636 /* -------------------------------------------------------------------------- */
2638 static void LoadArtworkInfoCache()
2640 InitCacheDirectory();
2642 if (artworkinfo_cache_old == NULL)
2644 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2646 /* try to load artwork info hash from already existing cache file */
2647 artworkinfo_cache_old = loadSetupFileHash(filename);
2649 /* if no artwork info cache file was found, start with empty hash */
2650 if (artworkinfo_cache_old == NULL)
2651 artworkinfo_cache_old = newSetupFileHash();
2656 if (artworkinfo_cache_new == NULL)
2657 artworkinfo_cache_new = newSetupFileHash();
2660 static void SaveArtworkInfoCache()
2662 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2664 InitCacheDirectory();
2666 saveSetupFileHash(artworkinfo_cache_new, filename);
2671 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2673 static char *prefix = NULL;
2675 checked_free(prefix);
2677 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2682 /* (identical to above function, but separate string buffer needed -- nasty) */
2683 static char *getCacheToken(char *prefix, char *suffix)
2685 static char *token = NULL;
2687 checked_free(token);
2689 token = getStringCat2WithSeparator(prefix, suffix, ".");
2694 static char *getFileTimestampString(char *filename)
2696 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2699 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2701 struct stat file_status;
2703 if (timestamp_string == NULL)
2706 if (stat(filename, &file_status) != 0) /* cannot stat file */
2709 return (file_status.st_mtime != atoi(timestamp_string));
2712 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2714 char *identifier = level_node->subdir;
2715 char *type_string = ARTWORK_DIRECTORY(type);
2716 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2717 char *token_main = getCacheToken(token_prefix, "CACHED");
2718 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2719 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2720 TreeInfo *artwork_info = NULL;
2722 if (!use_artworkinfo_cache)
2729 artwork_info = newTreeInfo();
2730 setTreeInfoToDefaults(artwork_info, type);
2732 /* set all structure fields according to the token/value pairs */
2733 ldi = *artwork_info;
2734 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2736 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2737 char *value = getHashEntry(artworkinfo_cache_old, token);
2739 setSetupInfo(artworkinfo_tokens, i, value);
2741 /* check if cache entry for this item is invalid or incomplete */
2744 Error(ERR_WARN, "cache entry '%s' invalid", token);
2750 *artwork_info = ldi;
2755 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2756 LEVELINFO_FILENAME);
2757 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2758 ARTWORKINFO_FILENAME(type));
2760 /* check if corresponding "levelinfo.conf" file has changed */
2761 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2762 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2764 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2767 /* check if corresponding "<artworkinfo>.conf" file has changed */
2768 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2769 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2771 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2774 checked_free(filename_levelinfo);
2775 checked_free(filename_artworkinfo);
2778 if (!cached && artwork_info != NULL)
2780 freeTreeInfo(artwork_info);
2785 return artwork_info;
2788 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2789 LevelDirTree *level_node, int type)
2791 char *identifier = level_node->subdir;
2792 char *type_string = ARTWORK_DIRECTORY(type);
2793 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2794 char *token_main = getCacheToken(token_prefix, "CACHED");
2795 boolean set_cache_timestamps = TRUE;
2798 setHashEntry(artworkinfo_cache_new, token_main, "true");
2800 if (set_cache_timestamps)
2802 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2803 LEVELINFO_FILENAME);
2804 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2805 ARTWORKINFO_FILENAME(type));
2806 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2807 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2809 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2810 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2812 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2813 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2815 checked_free(filename_levelinfo);
2816 checked_free(filename_artworkinfo);
2817 checked_free(timestamp_levelinfo);
2818 checked_free(timestamp_artworkinfo);
2821 ldi = *artwork_info;
2822 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2824 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2825 char *value = getSetupValue(artworkinfo_tokens[i].type,
2826 artworkinfo_tokens[i].value);
2828 setHashEntry(artworkinfo_cache_new, token, value);
2833 /* -------------------------------------------------------------------------- */
2834 /* functions for loading level info and custom artwork info */
2835 /* -------------------------------------------------------------------------- */
2837 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2838 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2840 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2841 TreeInfo *node_parent,
2842 char *level_directory,
2843 char *directory_name)
2845 char *directory_path = getPath2(level_directory, directory_name);
2846 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2847 SetupFileHash *setup_file_hash;
2848 LevelDirTree *leveldir_new = NULL;
2851 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2852 if (!options.debug && !fileExists(filename))
2854 free(directory_path);
2860 setup_file_hash = loadSetupFileHash(filename);
2862 if (setup_file_hash == NULL)
2864 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2866 free(directory_path);
2872 leveldir_new = newTreeInfo();
2875 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2877 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2879 leveldir_new->subdir = getStringCopy(directory_name);
2881 /* set all structure fields according to the token/value pairs */
2882 ldi = *leveldir_new;
2883 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2884 setSetupInfo(levelinfo_tokens, i,
2885 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2886 *leveldir_new = ldi;
2888 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2889 setString(&leveldir_new->name, leveldir_new->subdir);
2891 if (leveldir_new->identifier == NULL)
2892 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2894 if (leveldir_new->name_sorting == NULL)
2895 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2897 if (node_parent == NULL) /* top level group */
2899 leveldir_new->basepath = getStringCopy(level_directory);
2900 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2902 else /* sub level group */
2904 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2905 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2908 leveldir_new->last_level =
2909 leveldir_new->first_level + leveldir_new->levels - 1;
2911 leveldir_new->in_user_dir =
2912 (!strEqual(leveldir_new->basepath, options.level_directory));
2914 /* adjust some settings if user's private level directory was detected */
2915 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2916 leveldir_new->in_user_dir &&
2917 (strEqual(leveldir_new->subdir, getLoginName()) ||
2918 strEqual(leveldir_new->name, getLoginName()) ||
2919 strEqual(leveldir_new->author, getRealName())))
2921 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2922 leveldir_new->readonly = FALSE;
2925 leveldir_new->user_defined =
2926 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2928 leveldir_new->color = LEVELCOLOR(leveldir_new);
2930 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2932 leveldir_new->handicap_level = /* set handicap to default value */
2933 (leveldir_new->user_defined || !leveldir_new->handicap ?
2934 leveldir_new->last_level : leveldir_new->first_level);
2936 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2938 pushTreeInfo(node_first, leveldir_new);
2940 freeSetupFileHash(setup_file_hash);
2942 if (leveldir_new->level_group)
2944 /* create node to link back to current level directory */
2945 createParentTreeInfoNode(leveldir_new);
2947 /* recursively step into sub-directory and look for more level series */
2948 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2949 leveldir_new, directory_path);
2952 free(directory_path);
2958 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2959 TreeInfo *node_parent,
2960 char *level_directory)
2963 DirectoryEntry *dir_entry;
2964 boolean valid_entry_found = FALSE;
2966 if ((dir = openDirectory(level_directory)) == NULL)
2968 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2973 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2975 char *directory_name = dir_entry->basename;
2976 char *directory_path = getPath2(level_directory, directory_name);
2978 /* skip entries for current and parent directory */
2979 if (strEqual(directory_name, ".") ||
2980 strEqual(directory_name, ".."))
2982 free(directory_path);
2987 /* find out if directory entry is itself a directory */
2988 if (!dir_entry->is_directory) /* not a directory */
2990 free(directory_path);
2995 free(directory_path);
2997 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2998 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2999 strEqual(directory_name, MUSIC_DIRECTORY))
3002 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3007 closeDirectory(dir);
3009 /* special case: top level directory may directly contain "levelinfo.conf" */
3010 if (node_parent == NULL && !valid_entry_found)
3012 /* check if this directory directly contains a file "levelinfo.conf" */
3013 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3014 level_directory, ".");
3017 if (!valid_entry_found)
3018 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3022 boolean AdjustGraphicsForEMC()
3024 boolean settings_changed = FALSE;
3026 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3027 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3029 return settings_changed;
3032 void LoadLevelInfo()
3034 InitUserLevelDirectory(getLoginName());
3036 DrawInitText("Loading level series", 120, FC_GREEN);
3038 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3039 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3041 leveldir_first = createTopTreeInfoNode(leveldir_first);
3043 /* after loading all level set information, clone the level directory tree
3044 and remove all level sets without levels (these may still contain artwork
3045 to be offered in the setup menu as "custom artwork", and are therefore
3046 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3047 leveldir_first_all = leveldir_first;
3048 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3050 AdjustGraphicsForEMC();
3052 /* before sorting, the first entries will be from the user directory */
3053 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3055 if (leveldir_first == NULL)
3056 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3058 sortTreeInfo(&leveldir_first);
3060 #if ENABLE_UNUSED_CODE
3061 dumpTreeInfo(leveldir_first, 0);
3065 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3066 TreeInfo *node_parent,
3067 char *base_directory,
3068 char *directory_name, int type)
3070 char *directory_path = getPath2(base_directory, directory_name);
3071 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3072 SetupFileHash *setup_file_hash = NULL;
3073 TreeInfo *artwork_new = NULL;
3076 if (fileExists(filename))
3077 setup_file_hash = loadSetupFileHash(filename);
3079 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3082 DirectoryEntry *dir_entry;
3083 boolean valid_file_found = FALSE;
3085 if ((dir = openDirectory(directory_path)) != NULL)
3087 while ((dir_entry = readDirectory(dir)) != NULL)
3089 if (FileIsArtworkType(dir_entry->filename, type))
3091 valid_file_found = TRUE;
3097 closeDirectory(dir);
3100 if (!valid_file_found)
3102 if (!strEqual(directory_name, "."))
3103 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3105 free(directory_path);
3112 artwork_new = newTreeInfo();
3115 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3117 setTreeInfoToDefaults(artwork_new, type);
3119 artwork_new->subdir = getStringCopy(directory_name);
3121 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3123 /* set all structure fields according to the token/value pairs */
3125 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3126 setSetupInfo(levelinfo_tokens, i,
3127 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3130 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3131 setString(&artwork_new->name, artwork_new->subdir);
3133 if (artwork_new->identifier == NULL)
3134 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3136 if (artwork_new->name_sorting == NULL)
3137 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3140 if (node_parent == NULL) /* top level group */
3142 artwork_new->basepath = getStringCopy(base_directory);
3143 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3145 else /* sub level group */
3147 artwork_new->basepath = getStringCopy(node_parent->basepath);
3148 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3151 artwork_new->in_user_dir =
3152 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3154 /* (may use ".sort_priority" from "setup_file_hash" above) */
3155 artwork_new->color = ARTWORKCOLOR(artwork_new);
3157 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3159 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3161 if (strEqual(artwork_new->subdir, "."))
3163 if (artwork_new->user_defined)
3165 setString(&artwork_new->identifier, "private");
3166 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3170 setString(&artwork_new->identifier, "classic");
3171 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3174 /* set to new values after changing ".sort_priority" */
3175 artwork_new->color = ARTWORKCOLOR(artwork_new);
3177 setString(&artwork_new->class_desc,
3178 getLevelClassDescription(artwork_new));
3182 setString(&artwork_new->identifier, artwork_new->subdir);
3185 setString(&artwork_new->name, artwork_new->identifier);
3186 setString(&artwork_new->name_sorting, artwork_new->name);
3189 pushTreeInfo(node_first, artwork_new);
3191 freeSetupFileHash(setup_file_hash);
3193 free(directory_path);
3199 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3200 TreeInfo *node_parent,
3201 char *base_directory, int type)
3204 DirectoryEntry *dir_entry;
3205 boolean valid_entry_found = FALSE;
3207 if ((dir = openDirectory(base_directory)) == NULL)
3209 /* display error if directory is main "options.graphics_directory" etc. */
3210 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3211 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3216 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3218 char *directory_name = dir_entry->basename;
3219 char *directory_path = getPath2(base_directory, directory_name);
3221 /* skip directory entries for current and parent directory */
3222 if (strEqual(directory_name, ".") ||
3223 strEqual(directory_name, ".."))
3225 free(directory_path);
3230 /* skip directory entries which are not a directory */
3231 if (!dir_entry->is_directory) /* not a directory */
3233 free(directory_path);
3238 free(directory_path);
3240 /* check if this directory contains artwork with or without config file */
3241 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3243 directory_name, type);
3246 closeDirectory(dir);
3248 /* check if this directory directly contains artwork itself */
3249 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3250 base_directory, ".",
3252 if (!valid_entry_found)
3253 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3257 static TreeInfo *getDummyArtworkInfo(int type)
3259 /* this is only needed when there is completely no artwork available */
3260 TreeInfo *artwork_new = newTreeInfo();
3262 setTreeInfoToDefaults(artwork_new, type);
3264 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3265 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3266 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3268 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3269 setString(&artwork_new->name, UNDEFINED_FILENAME);
3270 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3275 void LoadArtworkInfo()
3277 LoadArtworkInfoCache();
3279 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3281 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3282 options.graphics_directory,
3283 TREE_TYPE_GRAPHICS_DIR);
3284 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3285 getUserGraphicsDir(),
3286 TREE_TYPE_GRAPHICS_DIR);
3288 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3289 options.sounds_directory,
3290 TREE_TYPE_SOUNDS_DIR);
3291 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3293 TREE_TYPE_SOUNDS_DIR);
3295 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3296 options.music_directory,
3297 TREE_TYPE_MUSIC_DIR);
3298 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3300 TREE_TYPE_MUSIC_DIR);
3302 if (artwork.gfx_first == NULL)
3303 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3304 if (artwork.snd_first == NULL)
3305 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3306 if (artwork.mus_first == NULL)
3307 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3309 /* before sorting, the first entries will be from the user directory */
3310 artwork.gfx_current =
3311 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3312 if (artwork.gfx_current == NULL)
3313 artwork.gfx_current =
3314 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3315 if (artwork.gfx_current == NULL)
3316 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3318 artwork.snd_current =
3319 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3320 if (artwork.snd_current == NULL)
3321 artwork.snd_current =
3322 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3323 if (artwork.snd_current == NULL)
3324 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3326 artwork.mus_current =
3327 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3328 if (artwork.mus_current == NULL)
3329 artwork.mus_current =
3330 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3331 if (artwork.mus_current == NULL)
3332 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3334 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3335 artwork.snd_current_identifier = artwork.snd_current->identifier;
3336 artwork.mus_current_identifier = artwork.mus_current->identifier;
3338 #if ENABLE_UNUSED_CODE
3339 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3340 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3341 printf("music set == %s\n\n", artwork.mus_current_identifier);
3344 sortTreeInfo(&artwork.gfx_first);
3345 sortTreeInfo(&artwork.snd_first);
3346 sortTreeInfo(&artwork.mus_first);
3348 #if ENABLE_UNUSED_CODE
3349 dumpTreeInfo(artwork.gfx_first, 0);
3350 dumpTreeInfo(artwork.snd_first, 0);
3351 dumpTreeInfo(artwork.mus_first, 0);
3355 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3356 LevelDirTree *level_node)
3358 int type = (*artwork_node)->type;
3360 /* recursively check all level directories for artwork sub-directories */
3364 /* check all tree entries for artwork, but skip parent link entries */
3365 if (!level_node->parent_link)
3367 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3368 boolean cached = (artwork_new != NULL);
3372 pushTreeInfo(artwork_node, artwork_new);
3376 TreeInfo *topnode_last = *artwork_node;
3377 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3378 ARTWORK_DIRECTORY(type));
3380 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3382 if (topnode_last != *artwork_node) /* check for newly added node */
3384 artwork_new = *artwork_node;
3386 setString(&artwork_new->identifier, level_node->subdir);
3387 setString(&artwork_new->name, level_node->name);
3388 setString(&artwork_new->name_sorting, level_node->name_sorting);
3390 artwork_new->sort_priority = level_node->sort_priority;
3391 artwork_new->color = LEVELCOLOR(artwork_new);
3397 /* insert artwork info (from old cache or filesystem) into new cache */
3398 if (artwork_new != NULL)
3399 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3402 DrawInitText(level_node->name, 150, FC_YELLOW);
3404 if (level_node->node_group != NULL)
3405 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3407 level_node = level_node->next;
3411 void LoadLevelArtworkInfo()
3413 print_timestamp_init("LoadLevelArtworkInfo");
3415 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3417 print_timestamp_time("DrawTimeText");
3419 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3420 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3421 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3422 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3423 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3424 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3426 SaveArtworkInfoCache();
3428 print_timestamp_time("SaveArtworkInfoCache");
3430 /* needed for reloading level artwork not known at ealier stage */
3432 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3434 artwork.gfx_current =
3435 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3436 if (artwork.gfx_current == NULL)
3437 artwork.gfx_current =
3438 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3439 if (artwork.gfx_current == NULL)
3440 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3443 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3445 artwork.snd_current =
3446 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3447 if (artwork.snd_current == NULL)
3448 artwork.snd_current =
3449 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3450 if (artwork.snd_current == NULL)
3451 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3454 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3456 artwork.mus_current =
3457 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3458 if (artwork.mus_current == NULL)
3459 artwork.mus_current =
3460 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3461 if (artwork.mus_current == NULL)
3462 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3465 print_timestamp_time("getTreeInfoFromIdentifier");
3467 sortTreeInfo(&artwork.gfx_first);
3468 sortTreeInfo(&artwork.snd_first);
3469 sortTreeInfo(&artwork.mus_first);
3471 print_timestamp_time("sortTreeInfo");
3473 #if ENABLE_UNUSED_CODE
3474 dumpTreeInfo(artwork.gfx_first, 0);
3475 dumpTreeInfo(artwork.snd_first, 0);
3476 dumpTreeInfo(artwork.mus_first, 0);
3479 print_timestamp_done("LoadLevelArtworkInfo");
3482 static void SaveUserLevelInfo()
3484 LevelDirTree *level_info;
3489 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3491 if (!(file = fopen(filename, MODE_WRITE)))
3493 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3498 level_info = newTreeInfo();
3500 /* always start with reliable default values */
3501 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3503 setString(&level_info->name, getLoginName());
3504 setString(&level_info->author, getRealName());
3505 level_info->levels = 100;
3506 level_info->first_level = 1;
3508 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3510 fprintFileHeader(file, LEVELINFO_FILENAME);
3513 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3515 if (i == LEVELINFO_TOKEN_NAME ||
3516 i == LEVELINFO_TOKEN_AUTHOR ||
3517 i == LEVELINFO_TOKEN_LEVELS ||
3518 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3519 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3521 /* just to make things nicer :) */
3522 if (i == LEVELINFO_TOKEN_AUTHOR)
3523 fprintf(file, "\n");
3526 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3530 SetFilePermissions(filename, PERMS_PRIVATE);
3532 freeTreeInfo(level_info);
3536 char *getSetupValue(int type, void *value)
3538 static char value_string[MAX_LINE_LEN];
3546 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3550 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3554 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3555 *(int *)value == FALSE ? "off" : "on"));
3559 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3562 case TYPE_YES_NO_AUTO:
3563 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3564 *(int *)value == FALSE ? "no" : "yes"));
3568 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3572 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3576 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3580 sprintf(value_string, "%d", *(int *)value);
3584 if (*(char **)value == NULL)
3587 strcpy(value_string, *(char **)value);
3591 value_string[0] = '\0';
3595 if (type & TYPE_GHOSTED)
3596 strcpy(value_string, "n/a");
3598 return value_string;
3601 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3605 static char token_string[MAX_LINE_LEN];
3606 int token_type = token_info[token_nr].type;
3607 void *setup_value = token_info[token_nr].value;
3608 char *token_text = token_info[token_nr].text;
3609 char *value_string = getSetupValue(token_type, setup_value);
3611 /* build complete token string */
3612 sprintf(token_string, "%s%s", prefix, token_text);
3614 /* build setup entry line */
3615 line = getFormattedSetupEntry(token_string, value_string);
3617 if (token_type == TYPE_KEY_X11)
3619 Key key = *(Key *)setup_value;
3620 char *keyname = getKeyNameFromKey(key);
3622 /* add comment, if useful */
3623 if (!strEqual(keyname, "(undefined)") &&
3624 !strEqual(keyname, "(unknown)"))
3626 /* add at least one whitespace */
3628 for (i = strlen(line); i < token_comment_position; i++)
3632 strcat(line, keyname);
3639 void LoadLevelSetup_LastSeries()
3641 /* ----------------------------------------------------------------------- */
3642 /* ~/.<program>/levelsetup.conf */
3643 /* ----------------------------------------------------------------------- */
3645 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3646 SetupFileHash *level_setup_hash = NULL;
3648 /* always start with reliable default values */
3649 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3651 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3653 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3655 if (leveldir_current == NULL)
3656 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3659 if ((level_setup_hash = loadSetupFileHash(filename)))
3661 char *last_level_series =
3662 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3664 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3666 if (leveldir_current == NULL)
3667 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3669 freeSetupFileHash(level_setup_hash);
3672 Error(ERR_WARN, "using default setup values");
3677 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3679 /* ----------------------------------------------------------------------- */
3680 /* ~/.<program>/levelsetup.conf */
3681 /* ----------------------------------------------------------------------- */
3683 // check if the current level directory structure is available at this point
3684 if (leveldir_current == NULL)
3687 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3688 char *level_subdir = leveldir_current->subdir;
3691 InitUserDataDirectory();
3693 if (!(file = fopen(filename, MODE_WRITE)))
3695 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3702 fprintFileHeader(file, LEVELSETUP_FILENAME);
3704 if (deactivate_last_level_series)
3705 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3707 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3712 SetFilePermissions(filename, PERMS_PRIVATE);
3717 void SaveLevelSetup_LastSeries()
3719 SaveLevelSetup_LastSeries_Ext(FALSE);
3722 void SaveLevelSetup_LastSeries_Deactivate()
3724 SaveLevelSetup_LastSeries_Ext(TRUE);
3727 static void checkSeriesInfo()
3729 static char *level_directory = NULL;
3732 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3734 level_directory = getPath2((leveldir_current->in_user_dir ?
3735 getUserLevelDir(NULL) :
3736 options.level_directory),
3737 leveldir_current->fullpath);
3739 if ((dir = openDirectory(level_directory)) == NULL)
3741 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3746 closeDirectory(dir);
3749 void LoadLevelSetup_SeriesInfo()
3752 SetupFileHash *level_setup_hash = NULL;
3753 char *level_subdir = leveldir_current->subdir;
3756 /* always start with reliable default values */
3757 level_nr = leveldir_current->first_level;
3759 for (i = 0; i < MAX_LEVELS; i++)
3761 LevelStats_setPlayed(i, 0);
3762 LevelStats_setSolved(i, 0);
3765 checkSeriesInfo(leveldir_current);
3767 /* ----------------------------------------------------------------------- */
3768 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3769 /* ----------------------------------------------------------------------- */
3771 level_subdir = leveldir_current->subdir;
3773 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3775 if ((level_setup_hash = loadSetupFileHash(filename)))
3779 /* get last played level in this level set */
3781 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3785 level_nr = atoi(token_value);
3787 if (level_nr < leveldir_current->first_level)
3788 level_nr = leveldir_current->first_level;
3789 if (level_nr > leveldir_current->last_level)
3790 level_nr = leveldir_current->last_level;
3793 /* get handicap level in this level set */
3795 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3799 int level_nr = atoi(token_value);
3801 if (level_nr < leveldir_current->first_level)
3802 level_nr = leveldir_current->first_level;
3803 if (level_nr > leveldir_current->last_level + 1)
3804 level_nr = leveldir_current->last_level;
3806 if (leveldir_current->user_defined || !leveldir_current->handicap)
3807 level_nr = leveldir_current->last_level;
3809 leveldir_current->handicap_level = level_nr;
3812 /* get number of played and solved levels in this level set */
3814 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3816 char *token = HASH_ITERATION_TOKEN(itr);
3817 char *value = HASH_ITERATION_VALUE(itr);
3819 if (strlen(token) == 3 &&
3820 token[0] >= '0' && token[0] <= '9' &&
3821 token[1] >= '0' && token[1] <= '9' &&
3822 token[2] >= '0' && token[2] <= '9')
3824 int level_nr = atoi(token);
3827 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3829 value = strchr(value, ' ');
3832 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3835 END_HASH_ITERATION(hash, itr)
3837 freeSetupFileHash(level_setup_hash);
3840 Error(ERR_WARN, "using default setup values");
3845 void SaveLevelSetup_SeriesInfo()
3848 char *level_subdir = leveldir_current->subdir;
3849 char *level_nr_str = int2str(level_nr, 0);
3850 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3854 /* ----------------------------------------------------------------------- */
3855 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3856 /* ----------------------------------------------------------------------- */
3858 InitLevelSetupDirectory(level_subdir);
3860 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3862 if (!(file = fopen(filename, MODE_WRITE)))
3864 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3869 fprintFileHeader(file, LEVELSETUP_FILENAME);
3871 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3873 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3874 handicap_level_str));
3876 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3879 if (LevelStats_getPlayed(i) > 0 ||
3880 LevelStats_getSolved(i) > 0)
3885 sprintf(token, "%03d", i);
3886 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3888 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3894 SetFilePermissions(filename, PERMS_PRIVATE);
3899 int LevelStats_getPlayed(int nr)
3901 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3904 int LevelStats_getSolved(int nr)
3906 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3909 void LevelStats_setPlayed(int nr, int value)
3911 if (nr >= 0 && nr < MAX_LEVELS)
3912 level_stats[nr].played = value;
3915 void LevelStats_setSolved(int nr, int value)
3917 if (nr >= 0 && nr < MAX_LEVELS)
3918 level_stats[nr].solved = value;
3921 void LevelStats_incPlayed(int nr)
3923 if (nr >= 0 && nr < MAX_LEVELS)
3924 level_stats[nr].played++;
3927 void LevelStats_incSolved(int nr)
3929 if (nr >= 0 && nr < MAX_LEVELS)
3930 level_stats[nr].solved++;