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 static char *score_level_dir = NULL;
137 char *score_subdir = SCORES_DIRECTORY;
139 if (score_dir == NULL)
141 if (program.global_scores)
142 score_dir = getPath2(getCommonDataDir(), score_subdir);
144 score_dir = getPath2(getUserGameDataDir(), score_subdir);
147 if (level_subdir != NULL)
149 checked_free(score_level_dir);
151 score_level_dir = getPath2(score_dir, level_subdir);
153 return score_level_dir;
159 static char *getLevelSetupDir(char *level_subdir)
161 static char *levelsetup_dir = NULL;
162 char *data_dir = getUserGameDataDir();
163 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
165 checked_free(levelsetup_dir);
167 if (level_subdir != NULL)
168 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
170 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
172 return levelsetup_dir;
175 static char *getCacheDir()
177 static char *cache_dir = NULL;
179 if (cache_dir == NULL)
180 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
185 static char *getLevelDirFromTreeInfo(TreeInfo *node)
187 static char *level_dir = NULL;
190 return options.level_directory;
192 checked_free(level_dir);
194 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
195 options.level_directory), node->fullpath);
200 char *getCurrentLevelDir()
202 return getLevelDirFromTreeInfo(leveldir_current);
205 static char *getTapeDir(char *level_subdir)
207 static char *tape_dir = NULL;
208 char *data_dir = getUserGameDataDir();
209 char *tape_subdir = TAPES_DIRECTORY;
211 checked_free(tape_dir);
213 if (level_subdir != NULL)
214 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
216 tape_dir = getPath2(data_dir, tape_subdir);
221 static char *getSolutionTapeDir()
223 static char *tape_dir = NULL;
224 char *data_dir = getCurrentLevelDir();
225 char *tape_subdir = TAPES_DIRECTORY;
227 checked_free(tape_dir);
229 tape_dir = getPath2(data_dir, tape_subdir);
234 static char *getDefaultGraphicsDir(char *graphics_subdir)
236 static char *graphics_dir = NULL;
238 if (graphics_subdir == NULL)
239 return options.graphics_directory;
241 checked_free(graphics_dir);
243 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
248 static char *getDefaultSoundsDir(char *sounds_subdir)
250 static char *sounds_dir = NULL;
252 if (sounds_subdir == NULL)
253 return options.sounds_directory;
255 checked_free(sounds_dir);
257 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
262 static char *getDefaultMusicDir(char *music_subdir)
264 static char *music_dir = NULL;
266 if (music_subdir == NULL)
267 return options.music_directory;
269 checked_free(music_dir);
271 music_dir = getPath2(options.music_directory, music_subdir);
276 static char *getClassicArtworkSet(int type)
278 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
279 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
280 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
283 static char *getClassicArtworkDir(int type)
285 return (type == TREE_TYPE_GRAPHICS_DIR ?
286 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
287 type == TREE_TYPE_SOUNDS_DIR ?
288 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
289 type == TREE_TYPE_MUSIC_DIR ?
290 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
293 static char *getUserGraphicsDir()
295 static char *usergraphics_dir = NULL;
297 if (usergraphics_dir == NULL)
298 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
300 return usergraphics_dir;
303 static char *getUserSoundsDir()
305 static char *usersounds_dir = NULL;
307 if (usersounds_dir == NULL)
308 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
310 return usersounds_dir;
313 static char *getUserMusicDir()
315 static char *usermusic_dir = NULL;
317 if (usermusic_dir == NULL)
318 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
320 return usermusic_dir;
323 static char *getSetupArtworkDir(TreeInfo *ti)
325 static char *artwork_dir = NULL;
330 checked_free(artwork_dir);
332 artwork_dir = getPath2(ti->basepath, ti->fullpath);
337 char *setLevelArtworkDir(TreeInfo *ti)
339 char **artwork_path_ptr, **artwork_set_ptr;
340 TreeInfo *level_artwork;
342 if (ti == NULL || leveldir_current == NULL)
345 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
346 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
348 checked_free(*artwork_path_ptr);
350 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
352 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
357 No (or non-existing) artwork configured in "levelinfo.conf". This would
358 normally result in using the artwork configured in the setup menu. But
359 if an artwork subdirectory exists (which might contain custom artwork
360 or an artwork configuration file), this level artwork must be treated
361 as relative to the default "classic" artwork, not to the artwork that
362 is currently configured in the setup menu.
364 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
365 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
366 the real "classic" artwork from the original R'n'D (like "gfx_classic").
369 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
371 checked_free(*artwork_set_ptr);
373 if (directoryExists(dir))
375 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
376 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
380 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
381 *artwork_set_ptr = NULL;
387 return *artwork_set_ptr;
390 inline static char *getLevelArtworkSet(int type)
392 if (leveldir_current == NULL)
395 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
398 inline static char *getLevelArtworkDir(int type)
400 if (leveldir_current == NULL)
401 return UNDEFINED_FILENAME;
403 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
406 char *getProgramConfigFilename(char *command_filename_ptr)
408 char *command_filename_1 = getStringCopy(command_filename_ptr);
410 // strip trailing executable suffix from command filename
411 if (strSuffix(command_filename_1, ".exe"))
412 command_filename_1[strlen(command_filename_1) - 4] = '\0';
414 char *command_basepath = getBasePath(command_filename_ptr);
415 char *command_basename = getBaseNameNoSuffix(command_filename_ptr);
416 char *command_filename_2 = getPath2(command_basepath, command_basename);
418 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
419 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
421 // 1st try: look for config file that exactly matches the binary filename
422 if (fileExists(config_filename_1))
423 return config_filename_1;
425 // 2nd try: return config filename that matches binary filename without suffix
426 return config_filename_2;
429 char *getTapeFilename(int nr)
431 static char *filename = NULL;
432 char basename[MAX_FILENAME_LEN];
434 checked_free(filename);
436 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
437 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
442 char *getSolutionTapeFilename(int nr)
444 static char *filename = NULL;
445 char basename[MAX_FILENAME_LEN];
447 checked_free(filename);
449 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
450 filename = getPath2(getSolutionTapeDir(), basename);
452 if (!fileExists(filename))
454 static char *filename_sln = NULL;
456 checked_free(filename_sln);
458 sprintf(basename, "%03d.sln", nr);
459 filename_sln = getPath2(getSolutionTapeDir(), basename);
461 if (fileExists(filename_sln))
468 char *getScoreFilename(int nr)
470 static char *filename = NULL;
471 char basename[MAX_FILENAME_LEN];
473 checked_free(filename);
475 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
476 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
481 char *getSetupFilename()
483 static char *filename = NULL;
485 checked_free(filename);
487 filename = getPath2(getSetupDir(), SETUP_FILENAME);
492 char *getDefaultSetupFilename()
494 return program.config_filename;
497 char *getEditorSetupFilename()
499 static char *filename = NULL;
501 checked_free(filename);
502 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
504 if (fileExists(filename))
507 checked_free(filename);
508 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
513 char *getHelpAnimFilename()
515 static char *filename = NULL;
517 checked_free(filename);
519 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
524 char *getHelpTextFilename()
526 static char *filename = NULL;
528 checked_free(filename);
530 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
535 char *getLevelSetInfoFilename()
537 static char *filename = NULL;
552 for (i = 0; basenames[i] != NULL; i++)
554 checked_free(filename);
555 filename = getPath2(getCurrentLevelDir(), basenames[i]);
557 if (fileExists(filename))
564 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
566 static char basename[32];
568 sprintf(basename, "%s_%d.txt",
569 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
574 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
576 static char *filename = NULL;
578 boolean skip_setup_artwork = FALSE;
580 checked_free(filename);
582 basename = getLevelSetTitleMessageBasename(nr, initial);
584 if (!gfx.override_level_graphics)
586 /* 1st try: look for special artwork in current level series directory */
587 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
588 if (fileExists(filename))
593 /* 2nd try: look for message file in current level set directory */
594 filename = getPath2(getCurrentLevelDir(), basename);
595 if (fileExists(filename))
600 /* check if there is special artwork configured in level series config */
601 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
603 /* 3rd try: look for special artwork configured in level series config */
604 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
605 if (fileExists(filename))
610 /* take missing artwork configured in level set config from default */
611 skip_setup_artwork = TRUE;
615 if (!skip_setup_artwork)
617 /* 4th try: look for special artwork in configured artwork directory */
618 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
619 if (fileExists(filename))
625 /* 5th try: look for default artwork in new default artwork directory */
626 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
627 if (fileExists(filename))
632 /* 6th try: look for default artwork in old default artwork directory */
633 filename = getPath2(options.graphics_directory, basename);
634 if (fileExists(filename))
637 return NULL; /* cannot find specified artwork file anywhere */
640 static char *getCorrectedArtworkBasename(char *basename)
645 char *getCustomImageFilename(char *basename)
647 static char *filename = NULL;
648 boolean skip_setup_artwork = FALSE;
650 checked_free(filename);
652 basename = getCorrectedArtworkBasename(basename);
654 if (!gfx.override_level_graphics)
656 /* 1st try: look for special artwork in current level series directory */
657 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
658 if (fileExists(filename))
663 /* check if there is special artwork configured in level series config */
664 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
666 /* 2nd try: look for special artwork configured in level series config */
667 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
668 if (fileExists(filename))
673 /* take missing artwork configured in level set config from default */
674 skip_setup_artwork = TRUE;
678 if (!skip_setup_artwork)
680 /* 3rd try: look for special artwork in configured artwork directory */
681 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
682 if (fileExists(filename))
688 /* 4th try: look for default artwork in new default artwork directory */
689 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
690 if (fileExists(filename))
695 /* 5th try: look for default artwork in old default artwork directory */
696 filename = getImg2(options.graphics_directory, basename);
697 if (fileExists(filename))
700 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
705 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
708 /* 6th try: look for fallback artwork in old default artwork directory */
709 /* (needed to prevent errors when trying to access unused artwork files) */
710 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
711 if (fileExists(filename))
715 return NULL; /* cannot find specified artwork file anywhere */
718 char *getCustomSoundFilename(char *basename)
720 static char *filename = NULL;
721 boolean skip_setup_artwork = FALSE;
723 checked_free(filename);
725 basename = getCorrectedArtworkBasename(basename);
727 if (!gfx.override_level_sounds)
729 /* 1st try: look for special artwork in current level series directory */
730 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
731 if (fileExists(filename))
736 /* check if there is special artwork configured in level series config */
737 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
739 /* 2nd try: look for special artwork configured in level series config */
740 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
741 if (fileExists(filename))
746 /* take missing artwork configured in level set config from default */
747 skip_setup_artwork = TRUE;
751 if (!skip_setup_artwork)
753 /* 3rd try: look for special artwork in configured artwork directory */
754 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
755 if (fileExists(filename))
761 /* 4th try: look for default artwork in new default artwork directory */
762 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
763 if (fileExists(filename))
768 /* 5th try: look for default artwork in old default artwork directory */
769 filename = getPath2(options.sounds_directory, basename);
770 if (fileExists(filename))
773 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
778 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
781 /* 6th try: look for fallback artwork in old default artwork directory */
782 /* (needed to prevent errors when trying to access unused artwork files) */
783 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
784 if (fileExists(filename))
788 return NULL; /* cannot find specified artwork file anywhere */
791 char *getCustomMusicFilename(char *basename)
793 static char *filename = NULL;
794 boolean skip_setup_artwork = FALSE;
796 checked_free(filename);
798 basename = getCorrectedArtworkBasename(basename);
800 if (!gfx.override_level_music)
802 /* 1st try: look for special artwork in current level series directory */
803 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
804 if (fileExists(filename))
809 /* check if there is special artwork configured in level series config */
810 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
812 /* 2nd try: look for special artwork configured in level series config */
813 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
814 if (fileExists(filename))
819 /* take missing artwork configured in level set config from default */
820 skip_setup_artwork = TRUE;
824 if (!skip_setup_artwork)
826 /* 3rd try: look for special artwork in configured artwork directory */
827 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
828 if (fileExists(filename))
834 /* 4th try: look for default artwork in new default artwork directory */
835 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
836 if (fileExists(filename))
841 /* 5th try: look for default artwork in old default artwork directory */
842 filename = getPath2(options.music_directory, basename);
843 if (fileExists(filename))
846 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
851 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
854 /* 6th try: look for fallback artwork in old default artwork directory */
855 /* (needed to prevent errors when trying to access unused artwork files) */
856 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
857 if (fileExists(filename))
861 return NULL; /* cannot find specified artwork file anywhere */
864 char *getCustomArtworkFilename(char *basename, int type)
866 if (type == ARTWORK_TYPE_GRAPHICS)
867 return getCustomImageFilename(basename);
868 else if (type == ARTWORK_TYPE_SOUNDS)
869 return getCustomSoundFilename(basename);
870 else if (type == ARTWORK_TYPE_MUSIC)
871 return getCustomMusicFilename(basename);
873 return UNDEFINED_FILENAME;
876 char *getCustomArtworkConfigFilename(int type)
878 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
881 char *getCustomArtworkLevelConfigFilename(int type)
883 static char *filename = NULL;
885 checked_free(filename);
887 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
892 char *getCustomMusicDirectory(void)
894 static char *directory = NULL;
895 boolean skip_setup_artwork = FALSE;
897 checked_free(directory);
899 if (!gfx.override_level_music)
901 /* 1st try: look for special artwork in current level series directory */
902 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
903 if (directoryExists(directory))
908 /* check if there is special artwork configured in level series config */
909 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
911 /* 2nd try: look for special artwork configured in level series config */
912 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
913 if (directoryExists(directory))
918 /* take missing artwork configured in level set config from default */
919 skip_setup_artwork = TRUE;
923 if (!skip_setup_artwork)
925 /* 3rd try: look for special artwork in configured artwork directory */
926 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
927 if (directoryExists(directory))
933 /* 4th try: look for default artwork in new default artwork directory */
934 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
935 if (directoryExists(directory))
940 /* 5th try: look for default artwork in old default artwork directory */
941 directory = getStringCopy(options.music_directory);
942 if (directoryExists(directory))
945 return NULL; /* cannot find specified artwork file anywhere */
948 void InitTapeDirectory(char *level_subdir)
950 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
951 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
952 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
955 void InitScoreDirectory(char *level_subdir)
957 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
959 if (program.global_scores)
960 createDirectory(getCommonDataDir(), "common data", permissions);
962 createDirectory(getUserGameDataDir(), "user data", permissions);
964 createDirectory(getScoreDir(NULL), "main score", permissions);
965 createDirectory(getScoreDir(level_subdir), "level score", permissions);
968 static void SaveUserLevelInfo();
970 void InitUserLevelDirectory(char *level_subdir)
972 if (!directoryExists(getUserLevelDir(level_subdir)))
974 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
975 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
976 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
982 void InitLevelSetupDirectory(char *level_subdir)
984 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
985 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
986 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
989 void InitCacheDirectory()
991 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
992 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
996 /* ------------------------------------------------------------------------- */
997 /* some functions to handle lists of level and artwork directories */
998 /* ------------------------------------------------------------------------- */
1000 TreeInfo *newTreeInfo()
1002 return checked_calloc(sizeof(TreeInfo));
1005 TreeInfo *newTreeInfo_setDefaults(int type)
1007 TreeInfo *ti = newTreeInfo();
1009 setTreeInfoToDefaults(ti, type);
1014 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1016 node_new->next = *node_first;
1017 *node_first = node_new;
1020 int numTreeInfo(TreeInfo *node)
1033 boolean validLevelSeries(TreeInfo *node)
1035 return (node != NULL && !node->node_group && !node->parent_link);
1038 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1043 if (node->node_group) /* enter level group (step down into tree) */
1044 return getFirstValidTreeInfoEntry(node->node_group);
1045 else if (node->parent_link) /* skip start entry of level group */
1047 if (node->next) /* get first real level series entry */
1048 return getFirstValidTreeInfoEntry(node->next);
1049 else /* leave empty level group and go on */
1050 return getFirstValidTreeInfoEntry(node->node_parent->next);
1052 else /* this seems to be a regular level series */
1056 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1061 if (node->node_parent == NULL) /* top level group */
1062 return *node->node_top;
1063 else /* sub level group */
1064 return node->node_parent->node_group;
1067 int numTreeInfoInGroup(TreeInfo *node)
1069 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1072 int posTreeInfo(TreeInfo *node)
1074 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1079 if (node_cmp == node)
1083 node_cmp = node_cmp->next;
1089 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1091 TreeInfo *node_default = node;
1103 return node_default;
1106 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1108 if (identifier == NULL)
1113 if (node->node_group)
1115 TreeInfo *node_group;
1117 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1122 else if (!node->parent_link)
1124 if (strEqual(identifier, node->identifier))
1134 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1135 TreeInfo *node, boolean skip_sets_without_levels)
1142 if (!node->parent_link && !node->level_group &&
1143 skip_sets_without_levels && node->levels == 0)
1144 return cloneTreeNode(node_top, node_parent, node->next,
1145 skip_sets_without_levels);
1147 node_new = getTreeInfoCopy(node); /* copy complete node */
1149 node_new->node_top = node_top; /* correct top node link */
1150 node_new->node_parent = node_parent; /* correct parent node link */
1152 if (node->level_group)
1153 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1154 skip_sets_without_levels);
1156 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1157 skip_sets_without_levels);
1162 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1164 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1166 *ti_new = ti_cloned;
1169 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1171 boolean settings_changed = FALSE;
1175 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1176 !strEqual(node->graphics_set, node->graphics_set_ecs))
1178 setString(&node->graphics_set, node->graphics_set_ecs);
1179 settings_changed = TRUE;
1181 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1182 !strEqual(node->graphics_set, node->graphics_set_aga))
1184 setString(&node->graphics_set, node->graphics_set_aga);
1185 settings_changed = TRUE;
1188 if (node->node_group != NULL)
1189 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1194 return settings_changed;
1197 void dumpTreeInfo(TreeInfo *node, int depth)
1201 printf("Dumping TreeInfo:\n");
1205 for (i = 0; i < (depth + 1) * 3; i++)
1208 printf("'%s' / '%s'\n", node->identifier, node->name);
1211 // use for dumping artwork info tree
1212 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1213 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1216 if (node->node_group != NULL)
1217 dumpTreeInfo(node->node_group, depth + 1);
1223 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1224 int (*compare_function)(const void *,
1227 int num_nodes = numTreeInfo(*node_first);
1228 TreeInfo **sort_array;
1229 TreeInfo *node = *node_first;
1235 /* allocate array for sorting structure pointers */
1236 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1238 /* writing structure pointers to sorting array */
1239 while (i < num_nodes && node) /* double boundary check... */
1241 sort_array[i] = node;
1247 /* sorting the structure pointers in the sorting array */
1248 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1251 /* update the linkage of list elements with the sorted node array */
1252 for (i = 0; i < num_nodes - 1; i++)
1253 sort_array[i]->next = sort_array[i + 1];
1254 sort_array[num_nodes - 1]->next = NULL;
1256 /* update the linkage of the main list anchor pointer */
1257 *node_first = sort_array[0];
1261 /* now recursively sort the level group structures */
1265 if (node->node_group != NULL)
1266 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1272 void sortTreeInfo(TreeInfo **node_first)
1274 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1278 /* ========================================================================= */
1279 /* some stuff from "files.c" */
1280 /* ========================================================================= */
1282 #if defined(PLATFORM_WIN32)
1284 #define S_IRGRP S_IRUSR
1287 #define S_IROTH S_IRUSR
1290 #define S_IWGRP S_IWUSR
1293 #define S_IWOTH S_IWUSR
1296 #define S_IXGRP S_IXUSR
1299 #define S_IXOTH S_IXUSR
1302 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1307 #endif /* PLATFORM_WIN32 */
1309 /* file permissions for newly written files */
1310 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1311 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1312 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1314 #define MODE_W_PRIVATE (S_IWUSR)
1315 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1316 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1318 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1319 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1320 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1322 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1323 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1324 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1329 static char *dir = NULL;
1331 #if defined(PLATFORM_WIN32)
1334 dir = checked_malloc(MAX_PATH + 1);
1336 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1339 #elif defined(PLATFORM_UNIX)
1342 if ((dir = getenv("HOME")) == NULL)
1346 if ((pwd = getpwuid(getuid())) != NULL)
1347 dir = getStringCopy(pwd->pw_dir);
1359 char *getCommonDataDir(void)
1361 static char *common_data_dir = NULL;
1363 #if defined(PLATFORM_WIN32)
1364 if (common_data_dir == NULL)
1366 char *dir = checked_malloc(MAX_PATH + 1);
1368 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1369 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1370 common_data_dir = getPath2(dir, program.userdata_subdir);
1372 common_data_dir = options.rw_base_directory;
1375 if (common_data_dir == NULL)
1376 common_data_dir = options.rw_base_directory;
1379 return common_data_dir;
1382 char *getPersonalDataDir(void)
1384 static char *personal_data_dir = NULL;
1386 #if defined(PLATFORM_MACOSX)
1387 if (personal_data_dir == NULL)
1388 personal_data_dir = getPath2(getHomeDir(), "Documents");
1390 if (personal_data_dir == NULL)
1391 personal_data_dir = getHomeDir();
1394 return personal_data_dir;
1397 char *getUserGameDataDir(void)
1399 static char *user_game_data_dir = NULL;
1401 #if defined(PLATFORM_ANDROID)
1402 if (user_game_data_dir == NULL)
1403 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1405 if (user_game_data_dir == NULL)
1406 user_game_data_dir = getPath2(getPersonalDataDir(),
1407 program.userdata_subdir);
1410 return user_game_data_dir;
1415 return getUserGameDataDir();
1418 static mode_t posix_umask(mode_t mask)
1420 #if defined(PLATFORM_UNIX)
1427 static int posix_mkdir(const char *pathname, mode_t mode)
1429 #if defined(PLATFORM_WIN32)
1430 return mkdir(pathname);
1432 return mkdir(pathname, mode);
1436 static boolean posix_process_running_setgid()
1438 #if defined(PLATFORM_UNIX)
1439 return (getgid() != getegid());
1445 void createDirectory(char *dir, char *text, int permission_class)
1447 if (directoryExists(dir))
1450 /* leave "other" permissions in umask untouched, but ensure group parts
1451 of USERDATA_DIR_MODE are not masked */
1452 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1453 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1454 mode_t last_umask = posix_umask(0);
1455 mode_t group_umask = ~(dir_mode & S_IRWXG);
1456 int running_setgid = posix_process_running_setgid();
1458 if (permission_class == PERMS_PUBLIC)
1460 /* if we're setgid, protect files against "other" */
1461 /* else keep umask(0) to make the dir world-writable */
1464 posix_umask(last_umask & group_umask);
1466 dir_mode = DIR_PERMS_PUBLIC_ALL;
1469 if (posix_mkdir(dir, dir_mode) != 0)
1470 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1471 text, dir, strerror(errno));
1473 if (permission_class == PERMS_PUBLIC && !running_setgid)
1474 chmod(dir, dir_mode);
1476 posix_umask(last_umask); /* restore previous umask */
1479 void InitUserDataDirectory()
1481 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1484 void SetFilePermissions(char *filename, int permission_class)
1486 int running_setgid = posix_process_running_setgid();
1487 int perms = (permission_class == PERMS_PRIVATE ?
1488 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1490 if (permission_class == PERMS_PUBLIC && !running_setgid)
1491 perms = FILE_PERMS_PUBLIC_ALL;
1493 chmod(filename, perms);
1496 char *getCookie(char *file_type)
1498 static char cookie[MAX_COOKIE_LEN + 1];
1500 if (strlen(program.cookie_prefix) + 1 +
1501 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1502 return "[COOKIE ERROR]"; /* should never happen */
1504 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1505 program.cookie_prefix, file_type,
1506 program.version_major, program.version_minor);
1511 void fprintFileHeader(FILE *file, char *basename)
1513 char *prefix = "# ";
1516 fprintf_line_with_prefix(file, prefix, sep1, 77);
1517 fprintf(file, "%s%s\n", prefix, basename);
1518 fprintf_line_with_prefix(file, prefix, sep1, 77);
1519 fprintf(file, "\n");
1522 int getFileVersionFromCookieString(const char *cookie)
1524 const char *ptr_cookie1, *ptr_cookie2;
1525 const char *pattern1 = "_FILE_VERSION_";
1526 const char *pattern2 = "?.?";
1527 const int len_cookie = strlen(cookie);
1528 const int len_pattern1 = strlen(pattern1);
1529 const int len_pattern2 = strlen(pattern2);
1530 const int len_pattern = len_pattern1 + len_pattern2;
1531 int version_major, version_minor;
1533 if (len_cookie <= len_pattern)
1536 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1537 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1539 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1542 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1543 ptr_cookie2[1] != '.' ||
1544 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1547 version_major = ptr_cookie2[0] - '0';
1548 version_minor = ptr_cookie2[2] - '0';
1550 return VERSION_IDENT(version_major, version_minor, 0, 0);
1553 boolean checkCookieString(const char *cookie, const char *template)
1555 const char *pattern = "_FILE_VERSION_?.?";
1556 const int len_cookie = strlen(cookie);
1557 const int len_template = strlen(template);
1558 const int len_pattern = strlen(pattern);
1560 if (len_cookie != len_template)
1563 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1570 /* ------------------------------------------------------------------------- */
1571 /* setup file list and hash handling functions */
1572 /* ------------------------------------------------------------------------- */
1574 char *getFormattedSetupEntry(char *token, char *value)
1577 static char entry[MAX_LINE_LEN];
1579 /* if value is an empty string, just return token without value */
1583 /* start with the token and some spaces to format output line */
1584 sprintf(entry, "%s:", token);
1585 for (i = strlen(entry); i < token_value_position; i++)
1588 /* continue with the token's value */
1589 strcat(entry, value);
1594 SetupFileList *newSetupFileList(char *token, char *value)
1596 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1598 new->token = getStringCopy(token);
1599 new->value = getStringCopy(value);
1606 void freeSetupFileList(SetupFileList *list)
1611 checked_free(list->token);
1612 checked_free(list->value);
1615 freeSetupFileList(list->next);
1620 char *getListEntry(SetupFileList *list, char *token)
1625 if (strEqual(list->token, token))
1628 return getListEntry(list->next, token);
1631 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1636 if (strEqual(list->token, token))
1638 checked_free(list->value);
1640 list->value = getStringCopy(value);
1644 else if (list->next == NULL)
1645 return (list->next = newSetupFileList(token, value));
1647 return setListEntry(list->next, token, value);
1650 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1655 if (list->next == NULL)
1656 return (list->next = newSetupFileList(token, value));
1658 return addListEntry(list->next, token, value);
1661 #if ENABLE_UNUSED_CODE
1663 static void printSetupFileList(SetupFileList *list)
1668 printf("token: '%s'\n", list->token);
1669 printf("value: '%s'\n", list->value);
1671 printSetupFileList(list->next);
1677 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1678 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1679 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1680 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1682 #define insert_hash_entry hashtable_insert
1683 #define search_hash_entry hashtable_search
1684 #define change_hash_entry hashtable_change
1685 #define remove_hash_entry hashtable_remove
1688 unsigned int get_hash_from_key(void *key)
1693 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1694 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1695 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1696 it works better than many other constants, prime or not) has never been
1697 adequately explained.
1699 If you just want to have a good hash function, and cannot wait, djb2
1700 is one of the best string hash functions i know. It has excellent
1701 distribution and speed on many different sets of keys and table sizes.
1702 You are not likely to do better with one of the "well known" functions
1703 such as PJW, K&R, etc.
1705 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1708 char *str = (char *)key;
1709 unsigned int hash = 5381;
1712 while ((c = *str++))
1713 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1718 static int keys_are_equal(void *key1, void *key2)
1720 return (strEqual((char *)key1, (char *)key2));
1723 SetupFileHash *newSetupFileHash()
1725 SetupFileHash *new_hash =
1726 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1728 if (new_hash == NULL)
1729 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1734 void freeSetupFileHash(SetupFileHash *hash)
1739 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1742 char *getHashEntry(SetupFileHash *hash, char *token)
1747 return search_hash_entry(hash, token);
1750 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1757 value_copy = getStringCopy(value);
1759 /* change value; if it does not exist, insert it as new */
1760 if (!change_hash_entry(hash, token, value_copy))
1761 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1762 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1765 char *removeHashEntry(SetupFileHash *hash, char *token)
1770 return remove_hash_entry(hash, token);
1773 #if ENABLE_UNUSED_CODE
1775 static void printSetupFileHash(SetupFileHash *hash)
1777 BEGIN_HASH_ITERATION(hash, itr)
1779 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1780 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1782 END_HASH_ITERATION(hash, itr)
1787 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1788 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1789 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1791 static boolean token_value_separator_found = FALSE;
1792 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1793 static boolean token_value_separator_warning = FALSE;
1795 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1796 static boolean token_already_exists_warning = FALSE;
1799 static boolean getTokenValueFromSetupLineExt(char *line,
1800 char **token_ptr, char **value_ptr,
1801 char *filename, char *line_raw,
1803 boolean separator_required)
1805 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1806 char *token, *value, *line_ptr;
1808 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1809 if (line_raw == NULL)
1811 strncpy(line_copy, line, MAX_LINE_LEN);
1812 line_copy[MAX_LINE_LEN] = '\0';
1815 strcpy(line_raw_copy, line_copy);
1816 line_raw = line_raw_copy;
1819 /* cut trailing comment from input line */
1820 for (line_ptr = line; *line_ptr; line_ptr++)
1822 if (*line_ptr == '#')
1829 /* cut trailing whitespaces from input line */
1830 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1831 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1834 /* ignore empty lines */
1838 /* cut leading whitespaces from token */
1839 for (token = line; *token; token++)
1840 if (*token != ' ' && *token != '\t')
1843 /* start with empty value as reliable default */
1846 token_value_separator_found = FALSE;
1848 /* find end of token to determine start of value */
1849 for (line_ptr = token; *line_ptr; line_ptr++)
1851 /* first look for an explicit token/value separator, like ':' or '=' */
1852 if (*line_ptr == ':' || *line_ptr == '=')
1854 *line_ptr = '\0'; /* terminate token string */
1855 value = line_ptr + 1; /* set beginning of value */
1857 token_value_separator_found = TRUE;
1863 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1864 /* fallback: if no token/value separator found, also allow whitespaces */
1865 if (!token_value_separator_found && !separator_required)
1867 for (line_ptr = token; *line_ptr; line_ptr++)
1869 if (*line_ptr == ' ' || *line_ptr == '\t')
1871 *line_ptr = '\0'; /* terminate token string */
1872 value = line_ptr + 1; /* set beginning of value */
1874 token_value_separator_found = TRUE;
1880 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1881 if (token_value_separator_found)
1883 if (!token_value_separator_warning)
1885 Error(ERR_INFO_LINE, "-");
1887 if (filename != NULL)
1889 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1890 Error(ERR_INFO, "- config file: '%s'", filename);
1894 Error(ERR_WARN, "missing token/value separator(s):");
1897 token_value_separator_warning = TRUE;
1900 if (filename != NULL)
1901 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1903 Error(ERR_INFO, "- line: '%s'", line_raw);
1909 /* cut trailing whitespaces from token */
1910 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1911 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1914 /* cut leading whitespaces from value */
1915 for (; *value; value++)
1916 if (*value != ' ' && *value != '\t')
1925 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1927 /* while the internal (old) interface does not require a token/value
1928 separator (for downwards compatibility with existing files which
1929 don't use them), it is mandatory for the external (new) interface */
1931 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1934 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1935 boolean top_recursion_level, boolean is_hash)
1937 static SetupFileHash *include_filename_hash = NULL;
1938 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1939 char *token, *value, *line_ptr;
1940 void *insert_ptr = NULL;
1941 boolean read_continued_line = FALSE;
1943 int line_nr = 0, token_count = 0, include_count = 0;
1945 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1946 token_value_separator_warning = FALSE;
1949 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1950 token_already_exists_warning = FALSE;
1953 if (!(file = openFile(filename, MODE_READ)))
1955 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1960 /* use "insert pointer" to store list end for constant insertion complexity */
1962 insert_ptr = setup_file_data;
1964 /* on top invocation, create hash to mark included files (to prevent loops) */
1965 if (top_recursion_level)
1966 include_filename_hash = newSetupFileHash();
1968 /* mark this file as already included (to prevent including it again) */
1969 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1971 while (!checkEndOfFile(file))
1973 /* read next line of input file */
1974 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1977 /* check if line was completely read and is terminated by line break */
1978 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1981 /* cut trailing line break (this can be newline and/or carriage return) */
1982 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1983 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1986 /* copy raw input line for later use (mainly debugging output) */
1987 strcpy(line_raw, line);
1989 if (read_continued_line)
1991 /* append new line to existing line, if there is enough space */
1992 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1993 strcat(previous_line, line_ptr);
1995 strcpy(line, previous_line); /* copy storage buffer to line */
1997 read_continued_line = FALSE;
2000 /* if the last character is '\', continue at next line */
2001 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2003 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2004 strcpy(previous_line, line); /* copy line to storage buffer */
2006 read_continued_line = TRUE;
2011 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2012 line_raw, line_nr, FALSE))
2017 if (strEqual(token, "include"))
2019 if (getHashEntry(include_filename_hash, value) == NULL)
2021 char *basepath = getBasePath(filename);
2022 char *basename = getBaseName(value);
2023 char *filename_include = getPath2(basepath, basename);
2025 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2029 free(filename_include);
2035 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2042 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2044 getHashEntry((SetupFileHash *)setup_file_data, token);
2046 if (old_value != NULL)
2048 if (!token_already_exists_warning)
2050 Error(ERR_INFO_LINE, "-");
2051 Error(ERR_WARN, "duplicate token(s) found in config file:");
2052 Error(ERR_INFO, "- config file: '%s'", filename);
2054 token_already_exists_warning = TRUE;
2057 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2058 Error(ERR_INFO, " old value: '%s'", old_value);
2059 Error(ERR_INFO, " new value: '%s'", value);
2063 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2067 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2077 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2078 if (token_value_separator_warning)
2079 Error(ERR_INFO_LINE, "-");
2082 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2083 if (token_already_exists_warning)
2084 Error(ERR_INFO_LINE, "-");
2087 if (token_count == 0 && include_count == 0)
2088 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2090 if (top_recursion_level)
2091 freeSetupFileHash(include_filename_hash);
2096 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2100 if (!(file = fopen(filename, MODE_WRITE)))
2102 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2107 BEGIN_HASH_ITERATION(hash, itr)
2109 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2110 HASH_ITERATION_VALUE(itr)));
2112 END_HASH_ITERATION(hash, itr)
2117 SetupFileList *loadSetupFileList(char *filename)
2119 SetupFileList *setup_file_list = newSetupFileList("", "");
2120 SetupFileList *first_valid_list_entry;
2122 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2124 freeSetupFileList(setup_file_list);
2129 first_valid_list_entry = setup_file_list->next;
2131 /* free empty list header */
2132 setup_file_list->next = NULL;
2133 freeSetupFileList(setup_file_list);
2135 return first_valid_list_entry;
2138 SetupFileHash *loadSetupFileHash(char *filename)
2140 SetupFileHash *setup_file_hash = newSetupFileHash();
2142 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2144 freeSetupFileHash(setup_file_hash);
2149 return setup_file_hash;
2153 /* ========================================================================= */
2154 /* setup file stuff */
2155 /* ========================================================================= */
2157 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2158 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2159 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2161 /* level directory info */
2162 #define LEVELINFO_TOKEN_IDENTIFIER 0
2163 #define LEVELINFO_TOKEN_NAME 1
2164 #define LEVELINFO_TOKEN_NAME_SORTING 2
2165 #define LEVELINFO_TOKEN_AUTHOR 3
2166 #define LEVELINFO_TOKEN_YEAR 4
2167 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2168 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2169 #define LEVELINFO_TOKEN_TESTED_BY 7
2170 #define LEVELINFO_TOKEN_LEVELS 8
2171 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2172 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2173 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2174 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2175 #define LEVELINFO_TOKEN_READONLY 13
2176 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2177 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2178 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2179 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2180 #define LEVELINFO_TOKEN_MUSIC_SET 18
2181 #define LEVELINFO_TOKEN_FILENAME 19
2182 #define LEVELINFO_TOKEN_FILETYPE 20
2183 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2184 #define LEVELINFO_TOKEN_HANDICAP 22
2185 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2187 #define NUM_LEVELINFO_TOKENS 24
2189 static LevelDirTree ldi;
2191 static struct TokenInfo levelinfo_tokens[] =
2193 /* level directory info */
2194 { TYPE_STRING, &ldi.identifier, "identifier" },
2195 { TYPE_STRING, &ldi.name, "name" },
2196 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2197 { TYPE_STRING, &ldi.author, "author" },
2198 { TYPE_STRING, &ldi.year, "year" },
2199 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2200 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2201 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2202 { TYPE_INTEGER, &ldi.levels, "levels" },
2203 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2204 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2205 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2206 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2207 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2208 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2209 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2210 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2211 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2212 { TYPE_STRING, &ldi.music_set, "music_set" },
2213 { TYPE_STRING, &ldi.level_filename, "filename" },
2214 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2215 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2216 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2217 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2220 static struct TokenInfo artworkinfo_tokens[] =
2222 /* artwork directory info */
2223 { TYPE_STRING, &ldi.identifier, "identifier" },
2224 { TYPE_STRING, &ldi.subdir, "subdir" },
2225 { TYPE_STRING, &ldi.name, "name" },
2226 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2227 { TYPE_STRING, &ldi.author, "author" },
2228 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2229 { TYPE_STRING, &ldi.basepath, "basepath" },
2230 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2231 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2232 { TYPE_INTEGER, &ldi.color, "color" },
2233 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2238 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2242 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2243 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2244 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2245 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2248 ti->node_parent = NULL;
2249 ti->node_group = NULL;
2256 ti->fullpath = NULL;
2257 ti->basepath = NULL;
2258 ti->identifier = NULL;
2259 ti->name = getStringCopy(ANONYMOUS_NAME);
2260 ti->name_sorting = NULL;
2261 ti->author = getStringCopy(ANONYMOUS_NAME);
2264 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2265 ti->latest_engine = FALSE; /* default: get from level */
2266 ti->parent_link = FALSE;
2267 ti->in_user_dir = FALSE;
2268 ti->user_defined = FALSE;
2270 ti->class_desc = NULL;
2272 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2274 if (ti->type == TREE_TYPE_LEVEL_DIR)
2276 ti->imported_from = NULL;
2277 ti->imported_by = NULL;
2278 ti->tested_by = NULL;
2280 ti->graphics_set_ecs = NULL;
2281 ti->graphics_set_aga = NULL;
2282 ti->graphics_set = NULL;
2283 ti->sounds_set = NULL;
2284 ti->music_set = NULL;
2285 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2286 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2287 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2289 ti->level_filename = NULL;
2290 ti->level_filetype = NULL;
2292 ti->special_flags = NULL;
2295 ti->first_level = 0;
2297 ti->level_group = FALSE;
2298 ti->handicap_level = 0;
2299 ti->readonly = TRUE;
2300 ti->handicap = TRUE;
2301 ti->skip_levels = FALSE;
2305 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2309 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2311 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2316 /* copy all values from the parent structure */
2318 ti->type = parent->type;
2320 ti->node_top = parent->node_top;
2321 ti->node_parent = parent;
2322 ti->node_group = NULL;
2329 ti->fullpath = NULL;
2330 ti->basepath = NULL;
2331 ti->identifier = NULL;
2332 ti->name = getStringCopy(ANONYMOUS_NAME);
2333 ti->name_sorting = NULL;
2334 ti->author = getStringCopy(parent->author);
2335 ti->year = getStringCopy(parent->year);
2337 ti->sort_priority = parent->sort_priority;
2338 ti->latest_engine = parent->latest_engine;
2339 ti->parent_link = FALSE;
2340 ti->in_user_dir = parent->in_user_dir;
2341 ti->user_defined = parent->user_defined;
2342 ti->color = parent->color;
2343 ti->class_desc = getStringCopy(parent->class_desc);
2345 ti->infotext = getStringCopy(parent->infotext);
2347 if (ti->type == TREE_TYPE_LEVEL_DIR)
2349 ti->imported_from = getStringCopy(parent->imported_from);
2350 ti->imported_by = getStringCopy(parent->imported_by);
2351 ti->tested_by = getStringCopy(parent->tested_by);
2353 ti->graphics_set_ecs = NULL;
2354 ti->graphics_set_aga = NULL;
2355 ti->graphics_set = NULL;
2356 ti->sounds_set = NULL;
2357 ti->music_set = NULL;
2358 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2359 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2360 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2362 ti->level_filename = NULL;
2363 ti->level_filetype = NULL;
2365 ti->special_flags = getStringCopy(parent->special_flags);
2368 ti->first_level = 0;
2370 ti->level_group = FALSE;
2371 ti->handicap_level = 0;
2372 ti->readonly = parent->readonly;
2373 ti->handicap = TRUE;
2374 ti->skip_levels = FALSE;
2378 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2380 TreeInfo *ti_copy = newTreeInfo();
2382 /* copy all values from the original structure */
2384 ti_copy->type = ti->type;
2386 ti_copy->node_top = ti->node_top;
2387 ti_copy->node_parent = ti->node_parent;
2388 ti_copy->node_group = ti->node_group;
2389 ti_copy->next = ti->next;
2391 ti_copy->cl_first = ti->cl_first;
2392 ti_copy->cl_cursor = ti->cl_cursor;
2394 ti_copy->subdir = getStringCopy(ti->subdir);
2395 ti_copy->fullpath = getStringCopy(ti->fullpath);
2396 ti_copy->basepath = getStringCopy(ti->basepath);
2397 ti_copy->identifier = getStringCopy(ti->identifier);
2398 ti_copy->name = getStringCopy(ti->name);
2399 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2400 ti_copy->author = getStringCopy(ti->author);
2401 ti_copy->year = getStringCopy(ti->year);
2402 ti_copy->imported_from = getStringCopy(ti->imported_from);
2403 ti_copy->imported_by = getStringCopy(ti->imported_by);
2404 ti_copy->tested_by = getStringCopy(ti->tested_by);
2406 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2407 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2408 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2409 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2410 ti_copy->music_set = getStringCopy(ti->music_set);
2411 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2412 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2413 ti_copy->music_path = getStringCopy(ti->music_path);
2415 ti_copy->level_filename = getStringCopy(ti->level_filename);
2416 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2418 ti_copy->special_flags = getStringCopy(ti->special_flags);
2420 ti_copy->levels = ti->levels;
2421 ti_copy->first_level = ti->first_level;
2422 ti_copy->last_level = ti->last_level;
2423 ti_copy->sort_priority = ti->sort_priority;
2425 ti_copy->latest_engine = ti->latest_engine;
2427 ti_copy->level_group = ti->level_group;
2428 ti_copy->parent_link = ti->parent_link;
2429 ti_copy->in_user_dir = ti->in_user_dir;
2430 ti_copy->user_defined = ti->user_defined;
2431 ti_copy->readonly = ti->readonly;
2432 ti_copy->handicap = ti->handicap;
2433 ti_copy->skip_levels = ti->skip_levels;
2435 ti_copy->color = ti->color;
2436 ti_copy->class_desc = getStringCopy(ti->class_desc);
2437 ti_copy->handicap_level = ti->handicap_level;
2439 ti_copy->infotext = getStringCopy(ti->infotext);
2444 void freeTreeInfo(TreeInfo *ti)
2449 checked_free(ti->subdir);
2450 checked_free(ti->fullpath);
2451 checked_free(ti->basepath);
2452 checked_free(ti->identifier);
2454 checked_free(ti->name);
2455 checked_free(ti->name_sorting);
2456 checked_free(ti->author);
2457 checked_free(ti->year);
2459 checked_free(ti->class_desc);
2461 checked_free(ti->infotext);
2463 if (ti->type == TREE_TYPE_LEVEL_DIR)
2465 checked_free(ti->imported_from);
2466 checked_free(ti->imported_by);
2467 checked_free(ti->tested_by);
2469 checked_free(ti->graphics_set_ecs);
2470 checked_free(ti->graphics_set_aga);
2471 checked_free(ti->graphics_set);
2472 checked_free(ti->sounds_set);
2473 checked_free(ti->music_set);
2475 checked_free(ti->graphics_path);
2476 checked_free(ti->sounds_path);
2477 checked_free(ti->music_path);
2479 checked_free(ti->level_filename);
2480 checked_free(ti->level_filetype);
2482 checked_free(ti->special_flags);
2485 // recursively free child node
2487 freeTreeInfo(ti->node_group);
2489 // recursively free next node
2491 freeTreeInfo(ti->next);
2496 void setSetupInfo(struct TokenInfo *token_info,
2497 int token_nr, char *token_value)
2499 int token_type = token_info[token_nr].type;
2500 void *setup_value = token_info[token_nr].value;
2502 if (token_value == NULL)
2505 /* set setup field to corresponding token value */
2510 *(boolean *)setup_value = get_boolean_from_string(token_value);
2514 *(int *)setup_value = get_switch3_from_string(token_value);
2518 *(Key *)setup_value = getKeyFromKeyName(token_value);
2522 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2526 *(int *)setup_value = get_integer_from_string(token_value);
2530 checked_free(*(char **)setup_value);
2531 *(char **)setup_value = getStringCopy(token_value);
2539 static int compareTreeInfoEntries(const void *object1, const void *object2)
2541 const TreeInfo *entry1 = *((TreeInfo **)object1);
2542 const TreeInfo *entry2 = *((TreeInfo **)object2);
2543 int class_sorting1 = 0, class_sorting2 = 0;
2546 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2548 class_sorting1 = LEVELSORTING(entry1);
2549 class_sorting2 = LEVELSORTING(entry2);
2551 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2552 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2553 entry1->type == TREE_TYPE_MUSIC_DIR)
2555 class_sorting1 = ARTWORKSORTING(entry1);
2556 class_sorting2 = ARTWORKSORTING(entry2);
2559 if (entry1->parent_link || entry2->parent_link)
2560 compare_result = (entry1->parent_link ? -1 : +1);
2561 else if (entry1->sort_priority == entry2->sort_priority)
2563 char *name1 = getStringToLower(entry1->name_sorting);
2564 char *name2 = getStringToLower(entry2->name_sorting);
2566 compare_result = strcmp(name1, name2);
2571 else if (class_sorting1 == class_sorting2)
2572 compare_result = entry1->sort_priority - entry2->sort_priority;
2574 compare_result = class_sorting1 - class_sorting2;
2576 return compare_result;
2579 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2583 if (node_parent == NULL)
2586 ti_new = newTreeInfo();
2587 setTreeInfoToDefaults(ti_new, node_parent->type);
2589 ti_new->node_parent = node_parent;
2590 ti_new->parent_link = TRUE;
2592 setString(&ti_new->identifier, node_parent->identifier);
2593 setString(&ti_new->name, ".. (parent directory)");
2594 setString(&ti_new->name_sorting, ti_new->name);
2596 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2597 setString(&ti_new->fullpath, node_parent->fullpath);
2599 ti_new->sort_priority = node_parent->sort_priority;
2600 ti_new->latest_engine = node_parent->latest_engine;
2602 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2604 pushTreeInfo(&node_parent->node_group, ti_new);
2609 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2611 TreeInfo *ti_new, *ti_new2;
2613 if (node_first == NULL)
2616 ti_new = newTreeInfo();
2617 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2619 ti_new->node_parent = NULL;
2620 ti_new->parent_link = FALSE;
2622 setString(&ti_new->identifier, node_first->identifier);
2623 setString(&ti_new->name, "level sets");
2624 setString(&ti_new->name_sorting, ti_new->name);
2626 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2627 setString(&ti_new->fullpath, node_first->fullpath);
2629 ti_new->sort_priority = node_first->sort_priority;;
2630 ti_new->latest_engine = node_first->latest_engine;
2632 setString(&ti_new->class_desc, "level sets");
2634 ti_new->node_group = node_first;
2635 ti_new->level_group = TRUE;
2637 ti_new2 = createParentTreeInfoNode(ti_new);
2639 setString(&ti_new2->name, ".. (main menu)");
2640 setString(&ti_new2->name_sorting, ti_new2->name);
2646 /* -------------------------------------------------------------------------- */
2647 /* functions for handling level and custom artwork info cache */
2648 /* -------------------------------------------------------------------------- */
2650 static void LoadArtworkInfoCache()
2652 InitCacheDirectory();
2654 if (artworkinfo_cache_old == NULL)
2656 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2658 /* try to load artwork info hash from already existing cache file */
2659 artworkinfo_cache_old = loadSetupFileHash(filename);
2661 /* if no artwork info cache file was found, start with empty hash */
2662 if (artworkinfo_cache_old == NULL)
2663 artworkinfo_cache_old = newSetupFileHash();
2668 if (artworkinfo_cache_new == NULL)
2669 artworkinfo_cache_new = newSetupFileHash();
2672 static void SaveArtworkInfoCache()
2674 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2676 InitCacheDirectory();
2678 saveSetupFileHash(artworkinfo_cache_new, filename);
2683 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2685 static char *prefix = NULL;
2687 checked_free(prefix);
2689 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2694 /* (identical to above function, but separate string buffer needed -- nasty) */
2695 static char *getCacheToken(char *prefix, char *suffix)
2697 static char *token = NULL;
2699 checked_free(token);
2701 token = getStringCat2WithSeparator(prefix, suffix, ".");
2706 static char *getFileTimestampString(char *filename)
2708 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2711 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2713 struct stat file_status;
2715 if (timestamp_string == NULL)
2718 if (stat(filename, &file_status) != 0) /* cannot stat file */
2721 return (file_status.st_mtime != atoi(timestamp_string));
2724 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2726 char *identifier = level_node->subdir;
2727 char *type_string = ARTWORK_DIRECTORY(type);
2728 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2729 char *token_main = getCacheToken(token_prefix, "CACHED");
2730 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2731 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2732 TreeInfo *artwork_info = NULL;
2734 if (!use_artworkinfo_cache)
2741 artwork_info = newTreeInfo();
2742 setTreeInfoToDefaults(artwork_info, type);
2744 /* set all structure fields according to the token/value pairs */
2745 ldi = *artwork_info;
2746 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2748 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2749 char *value = getHashEntry(artworkinfo_cache_old, token);
2751 setSetupInfo(artworkinfo_tokens, i, value);
2753 /* check if cache entry for this item is invalid or incomplete */
2756 Error(ERR_WARN, "cache entry '%s' invalid", token);
2762 *artwork_info = ldi;
2767 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2768 LEVELINFO_FILENAME);
2769 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2770 ARTWORKINFO_FILENAME(type));
2772 /* check if corresponding "levelinfo.conf" file has changed */
2773 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2774 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2776 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2779 /* check if corresponding "<artworkinfo>.conf" file has changed */
2780 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2781 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2783 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2786 checked_free(filename_levelinfo);
2787 checked_free(filename_artworkinfo);
2790 if (!cached && artwork_info != NULL)
2792 freeTreeInfo(artwork_info);
2797 return artwork_info;
2800 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2801 LevelDirTree *level_node, int type)
2803 char *identifier = level_node->subdir;
2804 char *type_string = ARTWORK_DIRECTORY(type);
2805 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2806 char *token_main = getCacheToken(token_prefix, "CACHED");
2807 boolean set_cache_timestamps = TRUE;
2810 setHashEntry(artworkinfo_cache_new, token_main, "true");
2812 if (set_cache_timestamps)
2814 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2815 LEVELINFO_FILENAME);
2816 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2817 ARTWORKINFO_FILENAME(type));
2818 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2819 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2821 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2822 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2824 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2825 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2827 checked_free(filename_levelinfo);
2828 checked_free(filename_artworkinfo);
2829 checked_free(timestamp_levelinfo);
2830 checked_free(timestamp_artworkinfo);
2833 ldi = *artwork_info;
2834 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2836 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2837 char *value = getSetupValue(artworkinfo_tokens[i].type,
2838 artworkinfo_tokens[i].value);
2840 setHashEntry(artworkinfo_cache_new, token, value);
2845 /* -------------------------------------------------------------------------- */
2846 /* functions for loading level info and custom artwork info */
2847 /* -------------------------------------------------------------------------- */
2849 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2850 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2852 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2853 TreeInfo *node_parent,
2854 char *level_directory,
2855 char *directory_name)
2857 char *directory_path = getPath2(level_directory, directory_name);
2858 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2859 SetupFileHash *setup_file_hash;
2860 LevelDirTree *leveldir_new = NULL;
2863 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2864 if (!options.debug && !fileExists(filename))
2866 free(directory_path);
2872 setup_file_hash = loadSetupFileHash(filename);
2874 if (setup_file_hash == NULL)
2876 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2878 free(directory_path);
2884 leveldir_new = newTreeInfo();
2887 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2889 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2891 leveldir_new->subdir = getStringCopy(directory_name);
2893 /* set all structure fields according to the token/value pairs */
2894 ldi = *leveldir_new;
2895 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2896 setSetupInfo(levelinfo_tokens, i,
2897 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2898 *leveldir_new = ldi;
2900 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2901 setString(&leveldir_new->name, leveldir_new->subdir);
2903 if (leveldir_new->identifier == NULL)
2904 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2906 if (leveldir_new->name_sorting == NULL)
2907 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2909 if (node_parent == NULL) /* top level group */
2911 leveldir_new->basepath = getStringCopy(level_directory);
2912 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2914 else /* sub level group */
2916 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2917 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2920 leveldir_new->last_level =
2921 leveldir_new->first_level + leveldir_new->levels - 1;
2923 leveldir_new->in_user_dir =
2924 (!strEqual(leveldir_new->basepath, options.level_directory));
2926 /* adjust some settings if user's private level directory was detected */
2927 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2928 leveldir_new->in_user_dir &&
2929 (strEqual(leveldir_new->subdir, getLoginName()) ||
2930 strEqual(leveldir_new->name, getLoginName()) ||
2931 strEqual(leveldir_new->author, getRealName())))
2933 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2934 leveldir_new->readonly = FALSE;
2937 leveldir_new->user_defined =
2938 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2940 leveldir_new->color = LEVELCOLOR(leveldir_new);
2942 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2944 leveldir_new->handicap_level = /* set handicap to default value */
2945 (leveldir_new->user_defined || !leveldir_new->handicap ?
2946 leveldir_new->last_level : leveldir_new->first_level);
2948 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2950 pushTreeInfo(node_first, leveldir_new);
2952 freeSetupFileHash(setup_file_hash);
2954 if (leveldir_new->level_group)
2956 /* create node to link back to current level directory */
2957 createParentTreeInfoNode(leveldir_new);
2959 /* recursively step into sub-directory and look for more level series */
2960 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2961 leveldir_new, directory_path);
2964 free(directory_path);
2970 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2971 TreeInfo *node_parent,
2972 char *level_directory)
2975 DirectoryEntry *dir_entry;
2976 boolean valid_entry_found = FALSE;
2978 if ((dir = openDirectory(level_directory)) == NULL)
2980 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2985 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2987 char *directory_name = dir_entry->basename;
2988 char *directory_path = getPath2(level_directory, directory_name);
2990 /* skip entries for current and parent directory */
2991 if (strEqual(directory_name, ".") ||
2992 strEqual(directory_name, ".."))
2994 free(directory_path);
2999 /* find out if directory entry is itself a directory */
3000 if (!dir_entry->is_directory) /* not a directory */
3002 free(directory_path);
3007 free(directory_path);
3009 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3010 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3011 strEqual(directory_name, MUSIC_DIRECTORY))
3014 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3019 closeDirectory(dir);
3021 /* special case: top level directory may directly contain "levelinfo.conf" */
3022 if (node_parent == NULL && !valid_entry_found)
3024 /* check if this directory directly contains a file "levelinfo.conf" */
3025 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3026 level_directory, ".");
3029 if (!valid_entry_found)
3030 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3034 boolean AdjustGraphicsForEMC()
3036 boolean settings_changed = FALSE;
3038 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3039 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3041 return settings_changed;
3044 void LoadLevelInfo()
3046 InitUserLevelDirectory(getLoginName());
3048 DrawInitText("Loading level series", 120, FC_GREEN);
3050 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3051 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3053 leveldir_first = createTopTreeInfoNode(leveldir_first);
3055 /* after loading all level set information, clone the level directory tree
3056 and remove all level sets without levels (these may still contain artwork
3057 to be offered in the setup menu as "custom artwork", and are therefore
3058 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3059 leveldir_first_all = leveldir_first;
3060 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3062 AdjustGraphicsForEMC();
3064 /* before sorting, the first entries will be from the user directory */
3065 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3067 if (leveldir_first == NULL)
3068 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3070 sortTreeInfo(&leveldir_first);
3072 #if ENABLE_UNUSED_CODE
3073 dumpTreeInfo(leveldir_first, 0);
3077 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3078 TreeInfo *node_parent,
3079 char *base_directory,
3080 char *directory_name, int type)
3082 char *directory_path = getPath2(base_directory, directory_name);
3083 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3084 SetupFileHash *setup_file_hash = NULL;
3085 TreeInfo *artwork_new = NULL;
3088 if (fileExists(filename))
3089 setup_file_hash = loadSetupFileHash(filename);
3091 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3094 DirectoryEntry *dir_entry;
3095 boolean valid_file_found = FALSE;
3097 if ((dir = openDirectory(directory_path)) != NULL)
3099 while ((dir_entry = readDirectory(dir)) != NULL)
3101 if (FileIsArtworkType(dir_entry->filename, type))
3103 valid_file_found = TRUE;
3109 closeDirectory(dir);
3112 if (!valid_file_found)
3114 if (!strEqual(directory_name, "."))
3115 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3117 free(directory_path);
3124 artwork_new = newTreeInfo();
3127 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3129 setTreeInfoToDefaults(artwork_new, type);
3131 artwork_new->subdir = getStringCopy(directory_name);
3133 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3135 /* set all structure fields according to the token/value pairs */
3137 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3138 setSetupInfo(levelinfo_tokens, i,
3139 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3142 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3143 setString(&artwork_new->name, artwork_new->subdir);
3145 if (artwork_new->identifier == NULL)
3146 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3148 if (artwork_new->name_sorting == NULL)
3149 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3152 if (node_parent == NULL) /* top level group */
3154 artwork_new->basepath = getStringCopy(base_directory);
3155 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3157 else /* sub level group */
3159 artwork_new->basepath = getStringCopy(node_parent->basepath);
3160 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3163 artwork_new->in_user_dir =
3164 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3166 /* (may use ".sort_priority" from "setup_file_hash" above) */
3167 artwork_new->color = ARTWORKCOLOR(artwork_new);
3169 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3171 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3173 if (strEqual(artwork_new->subdir, "."))
3175 if (artwork_new->user_defined)
3177 setString(&artwork_new->identifier, "private");
3178 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3182 setString(&artwork_new->identifier, "classic");
3183 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3186 /* set to new values after changing ".sort_priority" */
3187 artwork_new->color = ARTWORKCOLOR(artwork_new);
3189 setString(&artwork_new->class_desc,
3190 getLevelClassDescription(artwork_new));
3194 setString(&artwork_new->identifier, artwork_new->subdir);
3197 setString(&artwork_new->name, artwork_new->identifier);
3198 setString(&artwork_new->name_sorting, artwork_new->name);
3201 pushTreeInfo(node_first, artwork_new);
3203 freeSetupFileHash(setup_file_hash);
3205 free(directory_path);
3211 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3212 TreeInfo *node_parent,
3213 char *base_directory, int type)
3216 DirectoryEntry *dir_entry;
3217 boolean valid_entry_found = FALSE;
3219 if ((dir = openDirectory(base_directory)) == NULL)
3221 /* display error if directory is main "options.graphics_directory" etc. */
3222 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3223 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3228 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3230 char *directory_name = dir_entry->basename;
3231 char *directory_path = getPath2(base_directory, directory_name);
3233 /* skip directory entries for current and parent directory */
3234 if (strEqual(directory_name, ".") ||
3235 strEqual(directory_name, ".."))
3237 free(directory_path);
3242 /* skip directory entries which are not a directory */
3243 if (!dir_entry->is_directory) /* not a directory */
3245 free(directory_path);
3250 free(directory_path);
3252 /* check if this directory contains artwork with or without config file */
3253 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3255 directory_name, type);
3258 closeDirectory(dir);
3260 /* check if this directory directly contains artwork itself */
3261 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3262 base_directory, ".",
3264 if (!valid_entry_found)
3265 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3269 static TreeInfo *getDummyArtworkInfo(int type)
3271 /* this is only needed when there is completely no artwork available */
3272 TreeInfo *artwork_new = newTreeInfo();
3274 setTreeInfoToDefaults(artwork_new, type);
3276 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3277 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3278 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3280 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3281 setString(&artwork_new->name, UNDEFINED_FILENAME);
3282 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3287 void LoadArtworkInfo()
3289 LoadArtworkInfoCache();
3291 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3293 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3294 options.graphics_directory,
3295 TREE_TYPE_GRAPHICS_DIR);
3296 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3297 getUserGraphicsDir(),
3298 TREE_TYPE_GRAPHICS_DIR);
3300 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3301 options.sounds_directory,
3302 TREE_TYPE_SOUNDS_DIR);
3303 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3305 TREE_TYPE_SOUNDS_DIR);
3307 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3308 options.music_directory,
3309 TREE_TYPE_MUSIC_DIR);
3310 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3312 TREE_TYPE_MUSIC_DIR);
3314 if (artwork.gfx_first == NULL)
3315 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3316 if (artwork.snd_first == NULL)
3317 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3318 if (artwork.mus_first == NULL)
3319 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3321 /* before sorting, the first entries will be from the user directory */
3322 artwork.gfx_current =
3323 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3324 if (artwork.gfx_current == NULL)
3325 artwork.gfx_current =
3326 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3327 if (artwork.gfx_current == NULL)
3328 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3330 artwork.snd_current =
3331 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3332 if (artwork.snd_current == NULL)
3333 artwork.snd_current =
3334 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3335 if (artwork.snd_current == NULL)
3336 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3338 artwork.mus_current =
3339 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3340 if (artwork.mus_current == NULL)
3341 artwork.mus_current =
3342 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3343 if (artwork.mus_current == NULL)
3344 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3346 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3347 artwork.snd_current_identifier = artwork.snd_current->identifier;
3348 artwork.mus_current_identifier = artwork.mus_current->identifier;
3350 #if ENABLE_UNUSED_CODE
3351 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3352 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3353 printf("music set == %s\n\n", artwork.mus_current_identifier);
3356 sortTreeInfo(&artwork.gfx_first);
3357 sortTreeInfo(&artwork.snd_first);
3358 sortTreeInfo(&artwork.mus_first);
3360 #if ENABLE_UNUSED_CODE
3361 dumpTreeInfo(artwork.gfx_first, 0);
3362 dumpTreeInfo(artwork.snd_first, 0);
3363 dumpTreeInfo(artwork.mus_first, 0);
3367 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3368 LevelDirTree *level_node)
3370 int type = (*artwork_node)->type;
3372 /* recursively check all level directories for artwork sub-directories */
3376 /* check all tree entries for artwork, but skip parent link entries */
3377 if (!level_node->parent_link)
3379 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3380 boolean cached = (artwork_new != NULL);
3384 pushTreeInfo(artwork_node, artwork_new);
3388 TreeInfo *topnode_last = *artwork_node;
3389 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3390 ARTWORK_DIRECTORY(type));
3392 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3394 if (topnode_last != *artwork_node) /* check for newly added node */
3396 artwork_new = *artwork_node;
3398 setString(&artwork_new->identifier, level_node->subdir);
3399 setString(&artwork_new->name, level_node->name);
3400 setString(&artwork_new->name_sorting, level_node->name_sorting);
3402 artwork_new->sort_priority = level_node->sort_priority;
3403 artwork_new->color = LEVELCOLOR(artwork_new);
3409 /* insert artwork info (from old cache or filesystem) into new cache */
3410 if (artwork_new != NULL)
3411 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3414 DrawInitText(level_node->name, 150, FC_YELLOW);
3416 if (level_node->node_group != NULL)
3417 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3419 level_node = level_node->next;
3423 void LoadLevelArtworkInfo()
3425 print_timestamp_init("LoadLevelArtworkInfo");
3427 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3429 print_timestamp_time("DrawTimeText");
3431 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3432 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3433 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3434 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3435 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3436 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3438 SaveArtworkInfoCache();
3440 print_timestamp_time("SaveArtworkInfoCache");
3442 /* needed for reloading level artwork not known at ealier stage */
3444 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3446 artwork.gfx_current =
3447 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3448 if (artwork.gfx_current == NULL)
3449 artwork.gfx_current =
3450 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3451 if (artwork.gfx_current == NULL)
3452 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3455 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3457 artwork.snd_current =
3458 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3459 if (artwork.snd_current == NULL)
3460 artwork.snd_current =
3461 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3462 if (artwork.snd_current == NULL)
3463 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3466 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3468 artwork.mus_current =
3469 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3470 if (artwork.mus_current == NULL)
3471 artwork.mus_current =
3472 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3473 if (artwork.mus_current == NULL)
3474 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3477 print_timestamp_time("getTreeInfoFromIdentifier");
3479 sortTreeInfo(&artwork.gfx_first);
3480 sortTreeInfo(&artwork.snd_first);
3481 sortTreeInfo(&artwork.mus_first);
3483 print_timestamp_time("sortTreeInfo");
3485 #if ENABLE_UNUSED_CODE
3486 dumpTreeInfo(artwork.gfx_first, 0);
3487 dumpTreeInfo(artwork.snd_first, 0);
3488 dumpTreeInfo(artwork.mus_first, 0);
3491 print_timestamp_done("LoadLevelArtworkInfo");
3494 static void SaveUserLevelInfo()
3496 LevelDirTree *level_info;
3501 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3503 if (!(file = fopen(filename, MODE_WRITE)))
3505 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3510 level_info = newTreeInfo();
3512 /* always start with reliable default values */
3513 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3515 setString(&level_info->name, getLoginName());
3516 setString(&level_info->author, getRealName());
3517 level_info->levels = 100;
3518 level_info->first_level = 1;
3520 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3522 fprintFileHeader(file, LEVELINFO_FILENAME);
3525 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3527 if (i == LEVELINFO_TOKEN_NAME ||
3528 i == LEVELINFO_TOKEN_AUTHOR ||
3529 i == LEVELINFO_TOKEN_LEVELS ||
3530 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3531 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3533 /* just to make things nicer :) */
3534 if (i == LEVELINFO_TOKEN_AUTHOR)
3535 fprintf(file, "\n");
3538 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3542 SetFilePermissions(filename, PERMS_PRIVATE);
3544 freeTreeInfo(level_info);
3548 char *getSetupValue(int type, void *value)
3550 static char value_string[MAX_LINE_LEN];
3558 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3562 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3566 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3567 *(int *)value == FALSE ? "off" : "on"));
3571 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3574 case TYPE_YES_NO_AUTO:
3575 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3576 *(int *)value == FALSE ? "no" : "yes"));
3580 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3584 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3588 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3592 sprintf(value_string, "%d", *(int *)value);
3596 if (*(char **)value == NULL)
3599 strcpy(value_string, *(char **)value);
3603 value_string[0] = '\0';
3607 if (type & TYPE_GHOSTED)
3608 strcpy(value_string, "n/a");
3610 return value_string;
3613 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3617 static char token_string[MAX_LINE_LEN];
3618 int token_type = token_info[token_nr].type;
3619 void *setup_value = token_info[token_nr].value;
3620 char *token_text = token_info[token_nr].text;
3621 char *value_string = getSetupValue(token_type, setup_value);
3623 /* build complete token string */
3624 sprintf(token_string, "%s%s", prefix, token_text);
3626 /* build setup entry line */
3627 line = getFormattedSetupEntry(token_string, value_string);
3629 if (token_type == TYPE_KEY_X11)
3631 Key key = *(Key *)setup_value;
3632 char *keyname = getKeyNameFromKey(key);
3634 /* add comment, if useful */
3635 if (!strEqual(keyname, "(undefined)") &&
3636 !strEqual(keyname, "(unknown)"))
3638 /* add at least one whitespace */
3640 for (i = strlen(line); i < token_comment_position; i++)
3644 strcat(line, keyname);
3651 void LoadLevelSetup_LastSeries()
3653 /* ----------------------------------------------------------------------- */
3654 /* ~/.<program>/levelsetup.conf */
3655 /* ----------------------------------------------------------------------- */
3657 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3658 SetupFileHash *level_setup_hash = NULL;
3660 /* always start with reliable default values */
3661 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3663 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3665 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3667 if (leveldir_current == NULL)
3668 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3671 if ((level_setup_hash = loadSetupFileHash(filename)))
3673 char *last_level_series =
3674 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3676 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3678 if (leveldir_current == NULL)
3679 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3681 freeSetupFileHash(level_setup_hash);
3684 Error(ERR_WARN, "using default setup values");
3689 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3691 /* ----------------------------------------------------------------------- */
3692 /* ~/.<program>/levelsetup.conf */
3693 /* ----------------------------------------------------------------------- */
3695 // check if the current level directory structure is available at this point
3696 if (leveldir_current == NULL)
3699 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3700 char *level_subdir = leveldir_current->subdir;
3703 InitUserDataDirectory();
3705 if (!(file = fopen(filename, MODE_WRITE)))
3707 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3714 fprintFileHeader(file, LEVELSETUP_FILENAME);
3716 if (deactivate_last_level_series)
3717 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3719 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3724 SetFilePermissions(filename, PERMS_PRIVATE);
3729 void SaveLevelSetup_LastSeries()
3731 SaveLevelSetup_LastSeries_Ext(FALSE);
3734 void SaveLevelSetup_LastSeries_Deactivate()
3736 SaveLevelSetup_LastSeries_Ext(TRUE);
3739 static void checkSeriesInfo()
3741 static char *level_directory = NULL;
3744 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3746 level_directory = getPath2((leveldir_current->in_user_dir ?
3747 getUserLevelDir(NULL) :
3748 options.level_directory),
3749 leveldir_current->fullpath);
3751 if ((dir = openDirectory(level_directory)) == NULL)
3753 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3758 closeDirectory(dir);
3761 void LoadLevelSetup_SeriesInfo()
3764 SetupFileHash *level_setup_hash = NULL;
3765 char *level_subdir = leveldir_current->subdir;
3768 /* always start with reliable default values */
3769 level_nr = leveldir_current->first_level;
3771 for (i = 0; i < MAX_LEVELS; i++)
3773 LevelStats_setPlayed(i, 0);
3774 LevelStats_setSolved(i, 0);
3779 /* ----------------------------------------------------------------------- */
3780 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3781 /* ----------------------------------------------------------------------- */
3783 level_subdir = leveldir_current->subdir;
3785 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3787 if ((level_setup_hash = loadSetupFileHash(filename)))
3791 /* get last played level in this level set */
3793 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3797 level_nr = atoi(token_value);
3799 if (level_nr < leveldir_current->first_level)
3800 level_nr = leveldir_current->first_level;
3801 if (level_nr > leveldir_current->last_level)
3802 level_nr = leveldir_current->last_level;
3805 /* get handicap level in this level set */
3807 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3811 int level_nr = atoi(token_value);
3813 if (level_nr < leveldir_current->first_level)
3814 level_nr = leveldir_current->first_level;
3815 if (level_nr > leveldir_current->last_level + 1)
3816 level_nr = leveldir_current->last_level;
3818 if (leveldir_current->user_defined || !leveldir_current->handicap)
3819 level_nr = leveldir_current->last_level;
3821 leveldir_current->handicap_level = level_nr;
3824 /* get number of played and solved levels in this level set */
3826 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3828 char *token = HASH_ITERATION_TOKEN(itr);
3829 char *value = HASH_ITERATION_VALUE(itr);
3831 if (strlen(token) == 3 &&
3832 token[0] >= '0' && token[0] <= '9' &&
3833 token[1] >= '0' && token[1] <= '9' &&
3834 token[2] >= '0' && token[2] <= '9')
3836 int level_nr = atoi(token);
3839 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3841 value = strchr(value, ' ');
3844 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3847 END_HASH_ITERATION(hash, itr)
3849 freeSetupFileHash(level_setup_hash);
3852 Error(ERR_WARN, "using default setup values");
3857 void SaveLevelSetup_SeriesInfo()
3860 char *level_subdir = leveldir_current->subdir;
3861 char *level_nr_str = int2str(level_nr, 0);
3862 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3866 /* ----------------------------------------------------------------------- */
3867 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3868 /* ----------------------------------------------------------------------- */
3870 InitLevelSetupDirectory(level_subdir);
3872 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3874 if (!(file = fopen(filename, MODE_WRITE)))
3876 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3881 fprintFileHeader(file, LEVELSETUP_FILENAME);
3883 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3885 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3886 handicap_level_str));
3888 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3891 if (LevelStats_getPlayed(i) > 0 ||
3892 LevelStats_getSolved(i) > 0)
3897 sprintf(token, "%03d", i);
3898 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3900 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3906 SetFilePermissions(filename, PERMS_PRIVATE);
3911 int LevelStats_getPlayed(int nr)
3913 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3916 int LevelStats_getSolved(int nr)
3918 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3921 void LevelStats_setPlayed(int nr, int value)
3923 if (nr >= 0 && nr < MAX_LEVELS)
3924 level_stats[nr].played = value;
3927 void LevelStats_setSolved(int nr, int value)
3929 if (nr >= 0 && nr < MAX_LEVELS)
3930 level_stats[nr].solved = value;
3933 void LevelStats_incPlayed(int nr)
3935 if (nr >= 0 && nr < MAX_LEVELS)
3936 level_stats[nr].played++;
3939 void LevelStats_incSolved(int nr)
3941 if (nr >= 0 && nr < MAX_LEVELS)
3942 level_stats[nr].solved++;