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_AndroidGetExternalStorageState() &
1404 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1405 SDL_AndroidGetExternalStoragePath() :
1406 SDL_AndroidGetInternalStoragePath());
1408 if (user_game_data_dir == NULL)
1409 user_game_data_dir = getPath2(getPersonalDataDir(),
1410 program.userdata_subdir);
1413 return user_game_data_dir;
1418 return getUserGameDataDir();
1421 static mode_t posix_umask(mode_t mask)
1423 #if defined(PLATFORM_UNIX)
1430 static int posix_mkdir(const char *pathname, mode_t mode)
1432 #if defined(PLATFORM_WIN32)
1433 return mkdir(pathname);
1435 return mkdir(pathname, mode);
1439 static boolean posix_process_running_setgid()
1441 #if defined(PLATFORM_UNIX)
1442 return (getgid() != getegid());
1448 void createDirectory(char *dir, char *text, int permission_class)
1450 if (directoryExists(dir))
1453 /* leave "other" permissions in umask untouched, but ensure group parts
1454 of USERDATA_DIR_MODE are not masked */
1455 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1456 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1457 mode_t last_umask = posix_umask(0);
1458 mode_t group_umask = ~(dir_mode & S_IRWXG);
1459 int running_setgid = posix_process_running_setgid();
1461 if (permission_class == PERMS_PUBLIC)
1463 /* if we're setgid, protect files against "other" */
1464 /* else keep umask(0) to make the dir world-writable */
1467 posix_umask(last_umask & group_umask);
1469 dir_mode = DIR_PERMS_PUBLIC_ALL;
1472 if (posix_mkdir(dir, dir_mode) != 0)
1473 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1474 text, dir, strerror(errno));
1476 if (permission_class == PERMS_PUBLIC && !running_setgid)
1477 chmod(dir, dir_mode);
1479 posix_umask(last_umask); /* restore previous umask */
1482 void InitUserDataDirectory()
1484 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1487 void SetFilePermissions(char *filename, int permission_class)
1489 int running_setgid = posix_process_running_setgid();
1490 int perms = (permission_class == PERMS_PRIVATE ?
1491 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1493 if (permission_class == PERMS_PUBLIC && !running_setgid)
1494 perms = FILE_PERMS_PUBLIC_ALL;
1496 chmod(filename, perms);
1499 char *getCookie(char *file_type)
1501 static char cookie[MAX_COOKIE_LEN + 1];
1503 if (strlen(program.cookie_prefix) + 1 +
1504 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1505 return "[COOKIE ERROR]"; /* should never happen */
1507 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1508 program.cookie_prefix, file_type,
1509 program.version_major, program.version_minor);
1514 void fprintFileHeader(FILE *file, char *basename)
1516 char *prefix = "# ";
1519 fprintf_line_with_prefix(file, prefix, sep1, 77);
1520 fprintf(file, "%s%s\n", prefix, basename);
1521 fprintf_line_with_prefix(file, prefix, sep1, 77);
1522 fprintf(file, "\n");
1525 int getFileVersionFromCookieString(const char *cookie)
1527 const char *ptr_cookie1, *ptr_cookie2;
1528 const char *pattern1 = "_FILE_VERSION_";
1529 const char *pattern2 = "?.?";
1530 const int len_cookie = strlen(cookie);
1531 const int len_pattern1 = strlen(pattern1);
1532 const int len_pattern2 = strlen(pattern2);
1533 const int len_pattern = len_pattern1 + len_pattern2;
1534 int version_major, version_minor;
1536 if (len_cookie <= len_pattern)
1539 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1540 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1542 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1545 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1546 ptr_cookie2[1] != '.' ||
1547 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1550 version_major = ptr_cookie2[0] - '0';
1551 version_minor = ptr_cookie2[2] - '0';
1553 return VERSION_IDENT(version_major, version_minor, 0, 0);
1556 boolean checkCookieString(const char *cookie, const char *template)
1558 const char *pattern = "_FILE_VERSION_?.?";
1559 const int len_cookie = strlen(cookie);
1560 const int len_template = strlen(template);
1561 const int len_pattern = strlen(pattern);
1563 if (len_cookie != len_template)
1566 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1573 /* ------------------------------------------------------------------------- */
1574 /* setup file list and hash handling functions */
1575 /* ------------------------------------------------------------------------- */
1577 char *getFormattedSetupEntry(char *token, char *value)
1580 static char entry[MAX_LINE_LEN];
1582 /* if value is an empty string, just return token without value */
1586 /* start with the token and some spaces to format output line */
1587 sprintf(entry, "%s:", token);
1588 for (i = strlen(entry); i < token_value_position; i++)
1591 /* continue with the token's value */
1592 strcat(entry, value);
1597 SetupFileList *newSetupFileList(char *token, char *value)
1599 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1601 new->token = getStringCopy(token);
1602 new->value = getStringCopy(value);
1609 void freeSetupFileList(SetupFileList *list)
1614 checked_free(list->token);
1615 checked_free(list->value);
1618 freeSetupFileList(list->next);
1623 char *getListEntry(SetupFileList *list, char *token)
1628 if (strEqual(list->token, token))
1631 return getListEntry(list->next, token);
1634 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1639 if (strEqual(list->token, token))
1641 checked_free(list->value);
1643 list->value = getStringCopy(value);
1647 else if (list->next == NULL)
1648 return (list->next = newSetupFileList(token, value));
1650 return setListEntry(list->next, token, value);
1653 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1658 if (list->next == NULL)
1659 return (list->next = newSetupFileList(token, value));
1661 return addListEntry(list->next, token, value);
1664 #if ENABLE_UNUSED_CODE
1666 static void printSetupFileList(SetupFileList *list)
1671 printf("token: '%s'\n", list->token);
1672 printf("value: '%s'\n", list->value);
1674 printSetupFileList(list->next);
1680 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1681 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1682 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1683 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1685 #define insert_hash_entry hashtable_insert
1686 #define search_hash_entry hashtable_search
1687 #define change_hash_entry hashtable_change
1688 #define remove_hash_entry hashtable_remove
1691 unsigned int get_hash_from_key(void *key)
1696 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1697 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1698 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1699 it works better than many other constants, prime or not) has never been
1700 adequately explained.
1702 If you just want to have a good hash function, and cannot wait, djb2
1703 is one of the best string hash functions i know. It has excellent
1704 distribution and speed on many different sets of keys and table sizes.
1705 You are not likely to do better with one of the "well known" functions
1706 such as PJW, K&R, etc.
1708 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1711 char *str = (char *)key;
1712 unsigned int hash = 5381;
1715 while ((c = *str++))
1716 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1721 static int keys_are_equal(void *key1, void *key2)
1723 return (strEqual((char *)key1, (char *)key2));
1726 SetupFileHash *newSetupFileHash()
1728 SetupFileHash *new_hash =
1729 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1731 if (new_hash == NULL)
1732 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1737 void freeSetupFileHash(SetupFileHash *hash)
1742 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1745 char *getHashEntry(SetupFileHash *hash, char *token)
1750 return search_hash_entry(hash, token);
1753 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1760 value_copy = getStringCopy(value);
1762 /* change value; if it does not exist, insert it as new */
1763 if (!change_hash_entry(hash, token, value_copy))
1764 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1765 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1768 char *removeHashEntry(SetupFileHash *hash, char *token)
1773 return remove_hash_entry(hash, token);
1776 #if ENABLE_UNUSED_CODE
1778 static void printSetupFileHash(SetupFileHash *hash)
1780 BEGIN_HASH_ITERATION(hash, itr)
1782 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1783 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1785 END_HASH_ITERATION(hash, itr)
1790 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1791 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1792 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1794 static boolean token_value_separator_found = FALSE;
1795 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1796 static boolean token_value_separator_warning = FALSE;
1798 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1799 static boolean token_already_exists_warning = FALSE;
1802 static boolean getTokenValueFromSetupLineExt(char *line,
1803 char **token_ptr, char **value_ptr,
1804 char *filename, char *line_raw,
1806 boolean separator_required)
1808 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1809 char *token, *value, *line_ptr;
1811 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1812 if (line_raw == NULL)
1814 strncpy(line_copy, line, MAX_LINE_LEN);
1815 line_copy[MAX_LINE_LEN] = '\0';
1818 strcpy(line_raw_copy, line_copy);
1819 line_raw = line_raw_copy;
1822 /* cut trailing comment from input line */
1823 for (line_ptr = line; *line_ptr; line_ptr++)
1825 if (*line_ptr == '#')
1832 /* cut trailing whitespaces from input line */
1833 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1834 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1837 /* ignore empty lines */
1841 /* cut leading whitespaces from token */
1842 for (token = line; *token; token++)
1843 if (*token != ' ' && *token != '\t')
1846 /* start with empty value as reliable default */
1849 token_value_separator_found = FALSE;
1851 /* find end of token to determine start of value */
1852 for (line_ptr = token; *line_ptr; line_ptr++)
1854 /* first look for an explicit token/value separator, like ':' or '=' */
1855 if (*line_ptr == ':' || *line_ptr == '=')
1857 *line_ptr = '\0'; /* terminate token string */
1858 value = line_ptr + 1; /* set beginning of value */
1860 token_value_separator_found = TRUE;
1866 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1867 /* fallback: if no token/value separator found, also allow whitespaces */
1868 if (!token_value_separator_found && !separator_required)
1870 for (line_ptr = token; *line_ptr; line_ptr++)
1872 if (*line_ptr == ' ' || *line_ptr == '\t')
1874 *line_ptr = '\0'; /* terminate token string */
1875 value = line_ptr + 1; /* set beginning of value */
1877 token_value_separator_found = TRUE;
1883 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1884 if (token_value_separator_found)
1886 if (!token_value_separator_warning)
1888 Error(ERR_INFO_LINE, "-");
1890 if (filename != NULL)
1892 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1893 Error(ERR_INFO, "- config file: '%s'", filename);
1897 Error(ERR_WARN, "missing token/value separator(s):");
1900 token_value_separator_warning = TRUE;
1903 if (filename != NULL)
1904 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1906 Error(ERR_INFO, "- line: '%s'", line_raw);
1912 /* cut trailing whitespaces from token */
1913 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1914 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1917 /* cut leading whitespaces from value */
1918 for (; *value; value++)
1919 if (*value != ' ' && *value != '\t')
1928 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1930 /* while the internal (old) interface does not require a token/value
1931 separator (for downwards compatibility with existing files which
1932 don't use them), it is mandatory for the external (new) interface */
1934 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1937 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1938 boolean top_recursion_level, boolean is_hash)
1940 static SetupFileHash *include_filename_hash = NULL;
1941 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1942 char *token, *value, *line_ptr;
1943 void *insert_ptr = NULL;
1944 boolean read_continued_line = FALSE;
1946 int line_nr = 0, token_count = 0, include_count = 0;
1948 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1949 token_value_separator_warning = FALSE;
1952 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1953 token_already_exists_warning = FALSE;
1956 if (!(file = openFile(filename, MODE_READ)))
1958 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
1963 /* use "insert pointer" to store list end for constant insertion complexity */
1965 insert_ptr = setup_file_data;
1967 /* on top invocation, create hash to mark included files (to prevent loops) */
1968 if (top_recursion_level)
1969 include_filename_hash = newSetupFileHash();
1971 /* mark this file as already included (to prevent including it again) */
1972 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1974 while (!checkEndOfFile(file))
1976 /* read next line of input file */
1977 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1980 /* check if line was completely read and is terminated by line break */
1981 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1984 /* cut trailing line break (this can be newline and/or carriage return) */
1985 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1986 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1989 /* copy raw input line for later use (mainly debugging output) */
1990 strcpy(line_raw, line);
1992 if (read_continued_line)
1994 /* append new line to existing line, if there is enough space */
1995 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1996 strcat(previous_line, line_ptr);
1998 strcpy(line, previous_line); /* copy storage buffer to line */
2000 read_continued_line = FALSE;
2003 /* if the last character is '\', continue at next line */
2004 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2006 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2007 strcpy(previous_line, line); /* copy line to storage buffer */
2009 read_continued_line = TRUE;
2014 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2015 line_raw, line_nr, FALSE))
2020 if (strEqual(token, "include"))
2022 if (getHashEntry(include_filename_hash, value) == NULL)
2024 char *basepath = getBasePath(filename);
2025 char *basename = getBaseName(value);
2026 char *filename_include = getPath2(basepath, basename);
2028 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2032 free(filename_include);
2038 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2045 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2047 getHashEntry((SetupFileHash *)setup_file_data, token);
2049 if (old_value != NULL)
2051 if (!token_already_exists_warning)
2053 Error(ERR_INFO_LINE, "-");
2054 Error(ERR_WARN, "duplicate token(s) found in config file:");
2055 Error(ERR_INFO, "- config file: '%s'", filename);
2057 token_already_exists_warning = TRUE;
2060 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2061 Error(ERR_INFO, " old value: '%s'", old_value);
2062 Error(ERR_INFO, " new value: '%s'", value);
2066 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2070 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2080 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2081 if (token_value_separator_warning)
2082 Error(ERR_INFO_LINE, "-");
2085 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2086 if (token_already_exists_warning)
2087 Error(ERR_INFO_LINE, "-");
2090 if (token_count == 0 && include_count == 0)
2091 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2093 if (top_recursion_level)
2094 freeSetupFileHash(include_filename_hash);
2099 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2103 if (!(file = fopen(filename, MODE_WRITE)))
2105 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2110 BEGIN_HASH_ITERATION(hash, itr)
2112 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2113 HASH_ITERATION_VALUE(itr)));
2115 END_HASH_ITERATION(hash, itr)
2120 SetupFileList *loadSetupFileList(char *filename)
2122 SetupFileList *setup_file_list = newSetupFileList("", "");
2123 SetupFileList *first_valid_list_entry;
2125 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2127 freeSetupFileList(setup_file_list);
2132 first_valid_list_entry = setup_file_list->next;
2134 /* free empty list header */
2135 setup_file_list->next = NULL;
2136 freeSetupFileList(setup_file_list);
2138 return first_valid_list_entry;
2141 SetupFileHash *loadSetupFileHash(char *filename)
2143 SetupFileHash *setup_file_hash = newSetupFileHash();
2145 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2147 freeSetupFileHash(setup_file_hash);
2152 return setup_file_hash;
2156 /* ========================================================================= */
2157 /* setup file stuff */
2158 /* ========================================================================= */
2160 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2161 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2162 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2164 /* level directory info */
2165 #define LEVELINFO_TOKEN_IDENTIFIER 0
2166 #define LEVELINFO_TOKEN_NAME 1
2167 #define LEVELINFO_TOKEN_NAME_SORTING 2
2168 #define LEVELINFO_TOKEN_AUTHOR 3
2169 #define LEVELINFO_TOKEN_YEAR 4
2170 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2171 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2172 #define LEVELINFO_TOKEN_TESTED_BY 7
2173 #define LEVELINFO_TOKEN_LEVELS 8
2174 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2175 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2176 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2177 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2178 #define LEVELINFO_TOKEN_READONLY 13
2179 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2180 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2181 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2182 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2183 #define LEVELINFO_TOKEN_MUSIC_SET 18
2184 #define LEVELINFO_TOKEN_FILENAME 19
2185 #define LEVELINFO_TOKEN_FILETYPE 20
2186 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2187 #define LEVELINFO_TOKEN_HANDICAP 22
2188 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2190 #define NUM_LEVELINFO_TOKENS 24
2192 static LevelDirTree ldi;
2194 static struct TokenInfo levelinfo_tokens[] =
2196 /* level directory info */
2197 { TYPE_STRING, &ldi.identifier, "identifier" },
2198 { TYPE_STRING, &ldi.name, "name" },
2199 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2200 { TYPE_STRING, &ldi.author, "author" },
2201 { TYPE_STRING, &ldi.year, "year" },
2202 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2203 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2204 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2205 { TYPE_INTEGER, &ldi.levels, "levels" },
2206 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2207 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2208 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2209 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2210 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2211 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2212 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2213 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2214 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2215 { TYPE_STRING, &ldi.music_set, "music_set" },
2216 { TYPE_STRING, &ldi.level_filename, "filename" },
2217 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2218 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2219 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2220 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2223 static struct TokenInfo artworkinfo_tokens[] =
2225 /* artwork directory info */
2226 { TYPE_STRING, &ldi.identifier, "identifier" },
2227 { TYPE_STRING, &ldi.subdir, "subdir" },
2228 { TYPE_STRING, &ldi.name, "name" },
2229 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2230 { TYPE_STRING, &ldi.author, "author" },
2231 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2232 { TYPE_STRING, &ldi.basepath, "basepath" },
2233 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2234 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2235 { TYPE_INTEGER, &ldi.color, "color" },
2236 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2241 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2245 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2246 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2247 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2248 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2251 ti->node_parent = NULL;
2252 ti->node_group = NULL;
2259 ti->fullpath = NULL;
2260 ti->basepath = NULL;
2261 ti->identifier = NULL;
2262 ti->name = getStringCopy(ANONYMOUS_NAME);
2263 ti->name_sorting = NULL;
2264 ti->author = getStringCopy(ANONYMOUS_NAME);
2267 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2268 ti->latest_engine = FALSE; /* default: get from level */
2269 ti->parent_link = FALSE;
2270 ti->in_user_dir = FALSE;
2271 ti->user_defined = FALSE;
2273 ti->class_desc = NULL;
2275 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2277 if (ti->type == TREE_TYPE_LEVEL_DIR)
2279 ti->imported_from = NULL;
2280 ti->imported_by = NULL;
2281 ti->tested_by = NULL;
2283 ti->graphics_set_ecs = NULL;
2284 ti->graphics_set_aga = NULL;
2285 ti->graphics_set = NULL;
2286 ti->sounds_set = NULL;
2287 ti->music_set = NULL;
2288 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2289 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2290 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2292 ti->level_filename = NULL;
2293 ti->level_filetype = NULL;
2295 ti->special_flags = NULL;
2298 ti->first_level = 0;
2300 ti->level_group = FALSE;
2301 ti->handicap_level = 0;
2302 ti->readonly = TRUE;
2303 ti->handicap = TRUE;
2304 ti->skip_levels = FALSE;
2308 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2312 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2314 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2319 /* copy all values from the parent structure */
2321 ti->type = parent->type;
2323 ti->node_top = parent->node_top;
2324 ti->node_parent = parent;
2325 ti->node_group = NULL;
2332 ti->fullpath = NULL;
2333 ti->basepath = NULL;
2334 ti->identifier = NULL;
2335 ti->name = getStringCopy(ANONYMOUS_NAME);
2336 ti->name_sorting = NULL;
2337 ti->author = getStringCopy(parent->author);
2338 ti->year = getStringCopy(parent->year);
2340 ti->sort_priority = parent->sort_priority;
2341 ti->latest_engine = parent->latest_engine;
2342 ti->parent_link = FALSE;
2343 ti->in_user_dir = parent->in_user_dir;
2344 ti->user_defined = parent->user_defined;
2345 ti->color = parent->color;
2346 ti->class_desc = getStringCopy(parent->class_desc);
2348 ti->infotext = getStringCopy(parent->infotext);
2350 if (ti->type == TREE_TYPE_LEVEL_DIR)
2352 ti->imported_from = getStringCopy(parent->imported_from);
2353 ti->imported_by = getStringCopy(parent->imported_by);
2354 ti->tested_by = getStringCopy(parent->tested_by);
2356 ti->graphics_set_ecs = NULL;
2357 ti->graphics_set_aga = NULL;
2358 ti->graphics_set = NULL;
2359 ti->sounds_set = NULL;
2360 ti->music_set = NULL;
2361 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2362 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2363 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2365 ti->level_filename = NULL;
2366 ti->level_filetype = NULL;
2368 ti->special_flags = getStringCopy(parent->special_flags);
2371 ti->first_level = 0;
2373 ti->level_group = FALSE;
2374 ti->handicap_level = 0;
2375 ti->readonly = parent->readonly;
2376 ti->handicap = TRUE;
2377 ti->skip_levels = FALSE;
2381 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2383 TreeInfo *ti_copy = newTreeInfo();
2385 /* copy all values from the original structure */
2387 ti_copy->type = ti->type;
2389 ti_copy->node_top = ti->node_top;
2390 ti_copy->node_parent = ti->node_parent;
2391 ti_copy->node_group = ti->node_group;
2392 ti_copy->next = ti->next;
2394 ti_copy->cl_first = ti->cl_first;
2395 ti_copy->cl_cursor = ti->cl_cursor;
2397 ti_copy->subdir = getStringCopy(ti->subdir);
2398 ti_copy->fullpath = getStringCopy(ti->fullpath);
2399 ti_copy->basepath = getStringCopy(ti->basepath);
2400 ti_copy->identifier = getStringCopy(ti->identifier);
2401 ti_copy->name = getStringCopy(ti->name);
2402 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2403 ti_copy->author = getStringCopy(ti->author);
2404 ti_copy->year = getStringCopy(ti->year);
2405 ti_copy->imported_from = getStringCopy(ti->imported_from);
2406 ti_copy->imported_by = getStringCopy(ti->imported_by);
2407 ti_copy->tested_by = getStringCopy(ti->tested_by);
2409 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2410 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2411 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2412 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2413 ti_copy->music_set = getStringCopy(ti->music_set);
2414 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2415 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2416 ti_copy->music_path = getStringCopy(ti->music_path);
2418 ti_copy->level_filename = getStringCopy(ti->level_filename);
2419 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2421 ti_copy->special_flags = getStringCopy(ti->special_flags);
2423 ti_copy->levels = ti->levels;
2424 ti_copy->first_level = ti->first_level;
2425 ti_copy->last_level = ti->last_level;
2426 ti_copy->sort_priority = ti->sort_priority;
2428 ti_copy->latest_engine = ti->latest_engine;
2430 ti_copy->level_group = ti->level_group;
2431 ti_copy->parent_link = ti->parent_link;
2432 ti_copy->in_user_dir = ti->in_user_dir;
2433 ti_copy->user_defined = ti->user_defined;
2434 ti_copy->readonly = ti->readonly;
2435 ti_copy->handicap = ti->handicap;
2436 ti_copy->skip_levels = ti->skip_levels;
2438 ti_copy->color = ti->color;
2439 ti_copy->class_desc = getStringCopy(ti->class_desc);
2440 ti_copy->handicap_level = ti->handicap_level;
2442 ti_copy->infotext = getStringCopy(ti->infotext);
2447 void freeTreeInfo(TreeInfo *ti)
2452 checked_free(ti->subdir);
2453 checked_free(ti->fullpath);
2454 checked_free(ti->basepath);
2455 checked_free(ti->identifier);
2457 checked_free(ti->name);
2458 checked_free(ti->name_sorting);
2459 checked_free(ti->author);
2460 checked_free(ti->year);
2462 checked_free(ti->class_desc);
2464 checked_free(ti->infotext);
2466 if (ti->type == TREE_TYPE_LEVEL_DIR)
2468 checked_free(ti->imported_from);
2469 checked_free(ti->imported_by);
2470 checked_free(ti->tested_by);
2472 checked_free(ti->graphics_set_ecs);
2473 checked_free(ti->graphics_set_aga);
2474 checked_free(ti->graphics_set);
2475 checked_free(ti->sounds_set);
2476 checked_free(ti->music_set);
2478 checked_free(ti->graphics_path);
2479 checked_free(ti->sounds_path);
2480 checked_free(ti->music_path);
2482 checked_free(ti->level_filename);
2483 checked_free(ti->level_filetype);
2485 checked_free(ti->special_flags);
2488 // recursively free child node
2490 freeTreeInfo(ti->node_group);
2492 // recursively free next node
2494 freeTreeInfo(ti->next);
2499 void setSetupInfo(struct TokenInfo *token_info,
2500 int token_nr, char *token_value)
2502 int token_type = token_info[token_nr].type;
2503 void *setup_value = token_info[token_nr].value;
2505 if (token_value == NULL)
2508 /* set setup field to corresponding token value */
2513 *(boolean *)setup_value = get_boolean_from_string(token_value);
2517 *(int *)setup_value = get_switch3_from_string(token_value);
2521 *(Key *)setup_value = getKeyFromKeyName(token_value);
2525 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2529 *(int *)setup_value = get_integer_from_string(token_value);
2533 checked_free(*(char **)setup_value);
2534 *(char **)setup_value = getStringCopy(token_value);
2542 static int compareTreeInfoEntries(const void *object1, const void *object2)
2544 const TreeInfo *entry1 = *((TreeInfo **)object1);
2545 const TreeInfo *entry2 = *((TreeInfo **)object2);
2546 int class_sorting1 = 0, class_sorting2 = 0;
2549 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2551 class_sorting1 = LEVELSORTING(entry1);
2552 class_sorting2 = LEVELSORTING(entry2);
2554 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2555 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2556 entry1->type == TREE_TYPE_MUSIC_DIR)
2558 class_sorting1 = ARTWORKSORTING(entry1);
2559 class_sorting2 = ARTWORKSORTING(entry2);
2562 if (entry1->parent_link || entry2->parent_link)
2563 compare_result = (entry1->parent_link ? -1 : +1);
2564 else if (entry1->sort_priority == entry2->sort_priority)
2566 char *name1 = getStringToLower(entry1->name_sorting);
2567 char *name2 = getStringToLower(entry2->name_sorting);
2569 compare_result = strcmp(name1, name2);
2574 else if (class_sorting1 == class_sorting2)
2575 compare_result = entry1->sort_priority - entry2->sort_priority;
2577 compare_result = class_sorting1 - class_sorting2;
2579 return compare_result;
2582 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2586 if (node_parent == NULL)
2589 ti_new = newTreeInfo();
2590 setTreeInfoToDefaults(ti_new, node_parent->type);
2592 ti_new->node_parent = node_parent;
2593 ti_new->parent_link = TRUE;
2595 setString(&ti_new->identifier, node_parent->identifier);
2596 setString(&ti_new->name, ".. (parent directory)");
2597 setString(&ti_new->name_sorting, ti_new->name);
2599 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2600 setString(&ti_new->fullpath, node_parent->fullpath);
2602 ti_new->sort_priority = node_parent->sort_priority;
2603 ti_new->latest_engine = node_parent->latest_engine;
2605 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2607 pushTreeInfo(&node_parent->node_group, ti_new);
2612 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2614 TreeInfo *ti_new, *ti_new2;
2616 if (node_first == NULL)
2619 ti_new = newTreeInfo();
2620 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2622 ti_new->node_parent = NULL;
2623 ti_new->parent_link = FALSE;
2625 setString(&ti_new->identifier, node_first->identifier);
2626 setString(&ti_new->name, "level sets");
2627 setString(&ti_new->name_sorting, ti_new->name);
2629 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2630 setString(&ti_new->fullpath, node_first->fullpath);
2632 ti_new->sort_priority = node_first->sort_priority;;
2633 ti_new->latest_engine = node_first->latest_engine;
2635 setString(&ti_new->class_desc, "level sets");
2637 ti_new->node_group = node_first;
2638 ti_new->level_group = TRUE;
2640 ti_new2 = createParentTreeInfoNode(ti_new);
2642 setString(&ti_new2->name, ".. (main menu)");
2643 setString(&ti_new2->name_sorting, ti_new2->name);
2649 /* -------------------------------------------------------------------------- */
2650 /* functions for handling level and custom artwork info cache */
2651 /* -------------------------------------------------------------------------- */
2653 static void LoadArtworkInfoCache()
2655 InitCacheDirectory();
2657 if (artworkinfo_cache_old == NULL)
2659 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2661 /* try to load artwork info hash from already existing cache file */
2662 artworkinfo_cache_old = loadSetupFileHash(filename);
2664 /* if no artwork info cache file was found, start with empty hash */
2665 if (artworkinfo_cache_old == NULL)
2666 artworkinfo_cache_old = newSetupFileHash();
2671 if (artworkinfo_cache_new == NULL)
2672 artworkinfo_cache_new = newSetupFileHash();
2675 static void SaveArtworkInfoCache()
2677 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2679 InitCacheDirectory();
2681 saveSetupFileHash(artworkinfo_cache_new, filename);
2686 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2688 static char *prefix = NULL;
2690 checked_free(prefix);
2692 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2697 /* (identical to above function, but separate string buffer needed -- nasty) */
2698 static char *getCacheToken(char *prefix, char *suffix)
2700 static char *token = NULL;
2702 checked_free(token);
2704 token = getStringCat2WithSeparator(prefix, suffix, ".");
2709 static char *getFileTimestampString(char *filename)
2711 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2714 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2716 struct stat file_status;
2718 if (timestamp_string == NULL)
2721 if (stat(filename, &file_status) != 0) /* cannot stat file */
2724 return (file_status.st_mtime != atoi(timestamp_string));
2727 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2729 char *identifier = level_node->subdir;
2730 char *type_string = ARTWORK_DIRECTORY(type);
2731 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2732 char *token_main = getCacheToken(token_prefix, "CACHED");
2733 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2734 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2735 TreeInfo *artwork_info = NULL;
2737 if (!use_artworkinfo_cache)
2744 artwork_info = newTreeInfo();
2745 setTreeInfoToDefaults(artwork_info, type);
2747 /* set all structure fields according to the token/value pairs */
2748 ldi = *artwork_info;
2749 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2751 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2752 char *value = getHashEntry(artworkinfo_cache_old, token);
2754 setSetupInfo(artworkinfo_tokens, i, value);
2756 /* check if cache entry for this item is invalid or incomplete */
2759 Error(ERR_WARN, "cache entry '%s' invalid", token);
2765 *artwork_info = ldi;
2770 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2771 LEVELINFO_FILENAME);
2772 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2773 ARTWORKINFO_FILENAME(type));
2775 /* check if corresponding "levelinfo.conf" file has changed */
2776 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2777 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2779 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2782 /* check if corresponding "<artworkinfo>.conf" file has changed */
2783 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2784 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2786 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2789 checked_free(filename_levelinfo);
2790 checked_free(filename_artworkinfo);
2793 if (!cached && artwork_info != NULL)
2795 freeTreeInfo(artwork_info);
2800 return artwork_info;
2803 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2804 LevelDirTree *level_node, int type)
2806 char *identifier = level_node->subdir;
2807 char *type_string = ARTWORK_DIRECTORY(type);
2808 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2809 char *token_main = getCacheToken(token_prefix, "CACHED");
2810 boolean set_cache_timestamps = TRUE;
2813 setHashEntry(artworkinfo_cache_new, token_main, "true");
2815 if (set_cache_timestamps)
2817 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2818 LEVELINFO_FILENAME);
2819 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2820 ARTWORKINFO_FILENAME(type));
2821 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2822 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2824 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2825 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2827 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2828 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2830 checked_free(filename_levelinfo);
2831 checked_free(filename_artworkinfo);
2832 checked_free(timestamp_levelinfo);
2833 checked_free(timestamp_artworkinfo);
2836 ldi = *artwork_info;
2837 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2839 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2840 char *value = getSetupValue(artworkinfo_tokens[i].type,
2841 artworkinfo_tokens[i].value);
2843 setHashEntry(artworkinfo_cache_new, token, value);
2848 /* -------------------------------------------------------------------------- */
2849 /* functions for loading level info and custom artwork info */
2850 /* -------------------------------------------------------------------------- */
2852 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2853 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2855 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2856 TreeInfo *node_parent,
2857 char *level_directory,
2858 char *directory_name)
2860 char *directory_path = getPath2(level_directory, directory_name);
2861 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2862 SetupFileHash *setup_file_hash;
2863 LevelDirTree *leveldir_new = NULL;
2866 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2867 if (!options.debug && !fileExists(filename))
2869 free(directory_path);
2875 setup_file_hash = loadSetupFileHash(filename);
2877 if (setup_file_hash == NULL)
2879 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2881 free(directory_path);
2887 leveldir_new = newTreeInfo();
2890 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2892 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2894 leveldir_new->subdir = getStringCopy(directory_name);
2896 /* set all structure fields according to the token/value pairs */
2897 ldi = *leveldir_new;
2898 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2899 setSetupInfo(levelinfo_tokens, i,
2900 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2901 *leveldir_new = ldi;
2903 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2904 setString(&leveldir_new->name, leveldir_new->subdir);
2906 if (leveldir_new->identifier == NULL)
2907 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2909 if (leveldir_new->name_sorting == NULL)
2910 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2912 if (node_parent == NULL) /* top level group */
2914 leveldir_new->basepath = getStringCopy(level_directory);
2915 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2917 else /* sub level group */
2919 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2920 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2923 leveldir_new->last_level =
2924 leveldir_new->first_level + leveldir_new->levels - 1;
2926 leveldir_new->in_user_dir =
2927 (!strEqual(leveldir_new->basepath, options.level_directory));
2929 /* adjust some settings if user's private level directory was detected */
2930 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2931 leveldir_new->in_user_dir &&
2932 (strEqual(leveldir_new->subdir, getLoginName()) ||
2933 strEqual(leveldir_new->name, getLoginName()) ||
2934 strEqual(leveldir_new->author, getRealName())))
2936 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2937 leveldir_new->readonly = FALSE;
2940 leveldir_new->user_defined =
2941 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2943 leveldir_new->color = LEVELCOLOR(leveldir_new);
2945 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2947 leveldir_new->handicap_level = /* set handicap to default value */
2948 (leveldir_new->user_defined || !leveldir_new->handicap ?
2949 leveldir_new->last_level : leveldir_new->first_level);
2951 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2953 pushTreeInfo(node_first, leveldir_new);
2955 freeSetupFileHash(setup_file_hash);
2957 if (leveldir_new->level_group)
2959 /* create node to link back to current level directory */
2960 createParentTreeInfoNode(leveldir_new);
2962 /* recursively step into sub-directory and look for more level series */
2963 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2964 leveldir_new, directory_path);
2967 free(directory_path);
2973 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2974 TreeInfo *node_parent,
2975 char *level_directory)
2978 DirectoryEntry *dir_entry;
2979 boolean valid_entry_found = FALSE;
2981 if ((dir = openDirectory(level_directory)) == NULL)
2983 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2988 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2990 char *directory_name = dir_entry->basename;
2991 char *directory_path = getPath2(level_directory, directory_name);
2993 /* skip entries for current and parent directory */
2994 if (strEqual(directory_name, ".") ||
2995 strEqual(directory_name, ".."))
2997 free(directory_path);
3002 /* find out if directory entry is itself a directory */
3003 if (!dir_entry->is_directory) /* not a directory */
3005 free(directory_path);
3010 free(directory_path);
3012 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3013 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3014 strEqual(directory_name, MUSIC_DIRECTORY))
3017 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3022 closeDirectory(dir);
3024 /* special case: top level directory may directly contain "levelinfo.conf" */
3025 if (node_parent == NULL && !valid_entry_found)
3027 /* check if this directory directly contains a file "levelinfo.conf" */
3028 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3029 level_directory, ".");
3032 if (!valid_entry_found)
3033 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3037 boolean AdjustGraphicsForEMC()
3039 boolean settings_changed = FALSE;
3041 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3042 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3044 return settings_changed;
3047 void LoadLevelInfo()
3049 InitUserLevelDirectory(getLoginName());
3051 DrawInitText("Loading level series", 120, FC_GREEN);
3053 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3054 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3056 leveldir_first = createTopTreeInfoNode(leveldir_first);
3058 /* after loading all level set information, clone the level directory tree
3059 and remove all level sets without levels (these may still contain artwork
3060 to be offered in the setup menu as "custom artwork", and are therefore
3061 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3062 leveldir_first_all = leveldir_first;
3063 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3065 AdjustGraphicsForEMC();
3067 /* before sorting, the first entries will be from the user directory */
3068 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3070 if (leveldir_first == NULL)
3071 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3073 sortTreeInfo(&leveldir_first);
3075 #if ENABLE_UNUSED_CODE
3076 dumpTreeInfo(leveldir_first, 0);
3080 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3081 TreeInfo *node_parent,
3082 char *base_directory,
3083 char *directory_name, int type)
3085 char *directory_path = getPath2(base_directory, directory_name);
3086 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3087 SetupFileHash *setup_file_hash = NULL;
3088 TreeInfo *artwork_new = NULL;
3091 if (fileExists(filename))
3092 setup_file_hash = loadSetupFileHash(filename);
3094 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3097 DirectoryEntry *dir_entry;
3098 boolean valid_file_found = FALSE;
3100 if ((dir = openDirectory(directory_path)) != NULL)
3102 while ((dir_entry = readDirectory(dir)) != NULL)
3104 if (FileIsArtworkType(dir_entry->filename, type))
3106 valid_file_found = TRUE;
3112 closeDirectory(dir);
3115 if (!valid_file_found)
3117 if (!strEqual(directory_name, "."))
3118 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3120 free(directory_path);
3127 artwork_new = newTreeInfo();
3130 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3132 setTreeInfoToDefaults(artwork_new, type);
3134 artwork_new->subdir = getStringCopy(directory_name);
3136 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3138 /* set all structure fields according to the token/value pairs */
3140 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3141 setSetupInfo(levelinfo_tokens, i,
3142 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3145 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3146 setString(&artwork_new->name, artwork_new->subdir);
3148 if (artwork_new->identifier == NULL)
3149 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3151 if (artwork_new->name_sorting == NULL)
3152 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3155 if (node_parent == NULL) /* top level group */
3157 artwork_new->basepath = getStringCopy(base_directory);
3158 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3160 else /* sub level group */
3162 artwork_new->basepath = getStringCopy(node_parent->basepath);
3163 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3166 artwork_new->in_user_dir =
3167 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3169 /* (may use ".sort_priority" from "setup_file_hash" above) */
3170 artwork_new->color = ARTWORKCOLOR(artwork_new);
3172 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3174 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3176 if (strEqual(artwork_new->subdir, "."))
3178 if (artwork_new->user_defined)
3180 setString(&artwork_new->identifier, "private");
3181 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3185 setString(&artwork_new->identifier, "classic");
3186 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3189 /* set to new values after changing ".sort_priority" */
3190 artwork_new->color = ARTWORKCOLOR(artwork_new);
3192 setString(&artwork_new->class_desc,
3193 getLevelClassDescription(artwork_new));
3197 setString(&artwork_new->identifier, artwork_new->subdir);
3200 setString(&artwork_new->name, artwork_new->identifier);
3201 setString(&artwork_new->name_sorting, artwork_new->name);
3204 pushTreeInfo(node_first, artwork_new);
3206 freeSetupFileHash(setup_file_hash);
3208 free(directory_path);
3214 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3215 TreeInfo *node_parent,
3216 char *base_directory, int type)
3219 DirectoryEntry *dir_entry;
3220 boolean valid_entry_found = FALSE;
3222 if ((dir = openDirectory(base_directory)) == NULL)
3224 /* display error if directory is main "options.graphics_directory" etc. */
3225 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3226 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3231 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3233 char *directory_name = dir_entry->basename;
3234 char *directory_path = getPath2(base_directory, directory_name);
3236 /* skip directory entries for current and parent directory */
3237 if (strEqual(directory_name, ".") ||
3238 strEqual(directory_name, ".."))
3240 free(directory_path);
3245 /* skip directory entries which are not a directory */
3246 if (!dir_entry->is_directory) /* not a directory */
3248 free(directory_path);
3253 free(directory_path);
3255 /* check if this directory contains artwork with or without config file */
3256 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3258 directory_name, type);
3261 closeDirectory(dir);
3263 /* check if this directory directly contains artwork itself */
3264 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3265 base_directory, ".",
3267 if (!valid_entry_found)
3268 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3272 static TreeInfo *getDummyArtworkInfo(int type)
3274 /* this is only needed when there is completely no artwork available */
3275 TreeInfo *artwork_new = newTreeInfo();
3277 setTreeInfoToDefaults(artwork_new, type);
3279 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3280 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3281 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3283 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3284 setString(&artwork_new->name, UNDEFINED_FILENAME);
3285 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3290 void LoadArtworkInfo()
3292 LoadArtworkInfoCache();
3294 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3296 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3297 options.graphics_directory,
3298 TREE_TYPE_GRAPHICS_DIR);
3299 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3300 getUserGraphicsDir(),
3301 TREE_TYPE_GRAPHICS_DIR);
3303 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3304 options.sounds_directory,
3305 TREE_TYPE_SOUNDS_DIR);
3306 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3308 TREE_TYPE_SOUNDS_DIR);
3310 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3311 options.music_directory,
3312 TREE_TYPE_MUSIC_DIR);
3313 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3315 TREE_TYPE_MUSIC_DIR);
3317 if (artwork.gfx_first == NULL)
3318 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3319 if (artwork.snd_first == NULL)
3320 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3321 if (artwork.mus_first == NULL)
3322 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3324 /* before sorting, the first entries will be from the user directory */
3325 artwork.gfx_current =
3326 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3327 if (artwork.gfx_current == NULL)
3328 artwork.gfx_current =
3329 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3330 if (artwork.gfx_current == NULL)
3331 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3333 artwork.snd_current =
3334 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3335 if (artwork.snd_current == NULL)
3336 artwork.snd_current =
3337 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3338 if (artwork.snd_current == NULL)
3339 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3341 artwork.mus_current =
3342 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3343 if (artwork.mus_current == NULL)
3344 artwork.mus_current =
3345 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3346 if (artwork.mus_current == NULL)
3347 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3349 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3350 artwork.snd_current_identifier = artwork.snd_current->identifier;
3351 artwork.mus_current_identifier = artwork.mus_current->identifier;
3353 #if ENABLE_UNUSED_CODE
3354 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3355 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3356 printf("music set == %s\n\n", artwork.mus_current_identifier);
3359 sortTreeInfo(&artwork.gfx_first);
3360 sortTreeInfo(&artwork.snd_first);
3361 sortTreeInfo(&artwork.mus_first);
3363 #if ENABLE_UNUSED_CODE
3364 dumpTreeInfo(artwork.gfx_first, 0);
3365 dumpTreeInfo(artwork.snd_first, 0);
3366 dumpTreeInfo(artwork.mus_first, 0);
3370 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3371 LevelDirTree *level_node)
3373 int type = (*artwork_node)->type;
3375 /* recursively check all level directories for artwork sub-directories */
3379 /* check all tree entries for artwork, but skip parent link entries */
3380 if (!level_node->parent_link)
3382 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3383 boolean cached = (artwork_new != NULL);
3387 pushTreeInfo(artwork_node, artwork_new);
3391 TreeInfo *topnode_last = *artwork_node;
3392 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3393 ARTWORK_DIRECTORY(type));
3395 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3397 if (topnode_last != *artwork_node) /* check for newly added node */
3399 artwork_new = *artwork_node;
3401 setString(&artwork_new->identifier, level_node->subdir);
3402 setString(&artwork_new->name, level_node->name);
3403 setString(&artwork_new->name_sorting, level_node->name_sorting);
3405 artwork_new->sort_priority = level_node->sort_priority;
3406 artwork_new->color = LEVELCOLOR(artwork_new);
3412 /* insert artwork info (from old cache or filesystem) into new cache */
3413 if (artwork_new != NULL)
3414 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3417 DrawInitText(level_node->name, 150, FC_YELLOW);
3419 if (level_node->node_group != NULL)
3420 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3422 level_node = level_node->next;
3426 void LoadLevelArtworkInfo()
3428 print_timestamp_init("LoadLevelArtworkInfo");
3430 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3432 print_timestamp_time("DrawTimeText");
3434 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3435 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3436 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3437 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3438 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3439 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3441 SaveArtworkInfoCache();
3443 print_timestamp_time("SaveArtworkInfoCache");
3445 /* needed for reloading level artwork not known at ealier stage */
3447 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3449 artwork.gfx_current =
3450 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3451 if (artwork.gfx_current == NULL)
3452 artwork.gfx_current =
3453 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3454 if (artwork.gfx_current == NULL)
3455 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3458 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3460 artwork.snd_current =
3461 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3462 if (artwork.snd_current == NULL)
3463 artwork.snd_current =
3464 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3465 if (artwork.snd_current == NULL)
3466 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3469 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3471 artwork.mus_current =
3472 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3473 if (artwork.mus_current == NULL)
3474 artwork.mus_current =
3475 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3476 if (artwork.mus_current == NULL)
3477 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3480 print_timestamp_time("getTreeInfoFromIdentifier");
3482 sortTreeInfo(&artwork.gfx_first);
3483 sortTreeInfo(&artwork.snd_first);
3484 sortTreeInfo(&artwork.mus_first);
3486 print_timestamp_time("sortTreeInfo");
3488 #if ENABLE_UNUSED_CODE
3489 dumpTreeInfo(artwork.gfx_first, 0);
3490 dumpTreeInfo(artwork.snd_first, 0);
3491 dumpTreeInfo(artwork.mus_first, 0);
3494 print_timestamp_done("LoadLevelArtworkInfo");
3497 static void SaveUserLevelInfo()
3499 LevelDirTree *level_info;
3504 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3506 if (!(file = fopen(filename, MODE_WRITE)))
3508 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3513 level_info = newTreeInfo();
3515 /* always start with reliable default values */
3516 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3518 setString(&level_info->name, getLoginName());
3519 setString(&level_info->author, getRealName());
3520 level_info->levels = 100;
3521 level_info->first_level = 1;
3523 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3525 fprintFileHeader(file, LEVELINFO_FILENAME);
3528 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3530 if (i == LEVELINFO_TOKEN_NAME ||
3531 i == LEVELINFO_TOKEN_AUTHOR ||
3532 i == LEVELINFO_TOKEN_LEVELS ||
3533 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3534 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3536 /* just to make things nicer :) */
3537 if (i == LEVELINFO_TOKEN_AUTHOR)
3538 fprintf(file, "\n");
3541 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3545 SetFilePermissions(filename, PERMS_PRIVATE);
3547 freeTreeInfo(level_info);
3551 char *getSetupValue(int type, void *value)
3553 static char value_string[MAX_LINE_LEN];
3561 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3565 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3569 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3570 *(int *)value == FALSE ? "off" : "on"));
3574 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3577 case TYPE_YES_NO_AUTO:
3578 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3579 *(int *)value == FALSE ? "no" : "yes"));
3583 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3587 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3591 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3595 sprintf(value_string, "%d", *(int *)value);
3599 if (*(char **)value == NULL)
3602 strcpy(value_string, *(char **)value);
3606 value_string[0] = '\0';
3610 if (type & TYPE_GHOSTED)
3611 strcpy(value_string, "n/a");
3613 return value_string;
3616 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3620 static char token_string[MAX_LINE_LEN];
3621 int token_type = token_info[token_nr].type;
3622 void *setup_value = token_info[token_nr].value;
3623 char *token_text = token_info[token_nr].text;
3624 char *value_string = getSetupValue(token_type, setup_value);
3626 /* build complete token string */
3627 sprintf(token_string, "%s%s", prefix, token_text);
3629 /* build setup entry line */
3630 line = getFormattedSetupEntry(token_string, value_string);
3632 if (token_type == TYPE_KEY_X11)
3634 Key key = *(Key *)setup_value;
3635 char *keyname = getKeyNameFromKey(key);
3637 /* add comment, if useful */
3638 if (!strEqual(keyname, "(undefined)") &&
3639 !strEqual(keyname, "(unknown)"))
3641 /* add at least one whitespace */
3643 for (i = strlen(line); i < token_comment_position; i++)
3647 strcat(line, keyname);
3654 void LoadLevelSetup_LastSeries()
3656 /* ----------------------------------------------------------------------- */
3657 /* ~/.<program>/levelsetup.conf */
3658 /* ----------------------------------------------------------------------- */
3660 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3661 SetupFileHash *level_setup_hash = NULL;
3663 /* always start with reliable default values */
3664 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3666 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3668 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3670 if (leveldir_current == NULL)
3671 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3674 if ((level_setup_hash = loadSetupFileHash(filename)))
3676 char *last_level_series =
3677 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3679 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3681 if (leveldir_current == NULL)
3682 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3684 freeSetupFileHash(level_setup_hash);
3688 Error(ERR_DEBUG, "using default setup values");
3694 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3696 /* ----------------------------------------------------------------------- */
3697 /* ~/.<program>/levelsetup.conf */
3698 /* ----------------------------------------------------------------------- */
3700 // check if the current level directory structure is available at this point
3701 if (leveldir_current == NULL)
3704 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3705 char *level_subdir = leveldir_current->subdir;
3708 InitUserDataDirectory();
3710 if (!(file = fopen(filename, MODE_WRITE)))
3712 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3719 fprintFileHeader(file, LEVELSETUP_FILENAME);
3721 if (deactivate_last_level_series)
3722 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3724 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3729 SetFilePermissions(filename, PERMS_PRIVATE);
3734 void SaveLevelSetup_LastSeries()
3736 SaveLevelSetup_LastSeries_Ext(FALSE);
3739 void SaveLevelSetup_LastSeries_Deactivate()
3741 SaveLevelSetup_LastSeries_Ext(TRUE);
3744 static void checkSeriesInfo()
3746 static char *level_directory = NULL;
3749 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3751 level_directory = getPath2((leveldir_current->in_user_dir ?
3752 getUserLevelDir(NULL) :
3753 options.level_directory),
3754 leveldir_current->fullpath);
3756 if ((dir = openDirectory(level_directory)) == NULL)
3758 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3763 closeDirectory(dir);
3766 void LoadLevelSetup_SeriesInfo()
3769 SetupFileHash *level_setup_hash = NULL;
3770 char *level_subdir = leveldir_current->subdir;
3773 /* always start with reliable default values */
3774 level_nr = leveldir_current->first_level;
3776 for (i = 0; i < MAX_LEVELS; i++)
3778 LevelStats_setPlayed(i, 0);
3779 LevelStats_setSolved(i, 0);
3784 /* ----------------------------------------------------------------------- */
3785 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3786 /* ----------------------------------------------------------------------- */
3788 level_subdir = leveldir_current->subdir;
3790 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3792 if ((level_setup_hash = loadSetupFileHash(filename)))
3796 /* get last played level in this level set */
3798 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3802 level_nr = atoi(token_value);
3804 if (level_nr < leveldir_current->first_level)
3805 level_nr = leveldir_current->first_level;
3806 if (level_nr > leveldir_current->last_level)
3807 level_nr = leveldir_current->last_level;
3810 /* get handicap level in this level set */
3812 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3816 int level_nr = atoi(token_value);
3818 if (level_nr < leveldir_current->first_level)
3819 level_nr = leveldir_current->first_level;
3820 if (level_nr > leveldir_current->last_level + 1)
3821 level_nr = leveldir_current->last_level;
3823 if (leveldir_current->user_defined || !leveldir_current->handicap)
3824 level_nr = leveldir_current->last_level;
3826 leveldir_current->handicap_level = level_nr;
3829 /* get number of played and solved levels in this level set */
3831 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3833 char *token = HASH_ITERATION_TOKEN(itr);
3834 char *value = HASH_ITERATION_VALUE(itr);
3836 if (strlen(token) == 3 &&
3837 token[0] >= '0' && token[0] <= '9' &&
3838 token[1] >= '0' && token[1] <= '9' &&
3839 token[2] >= '0' && token[2] <= '9')
3841 int level_nr = atoi(token);
3844 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3846 value = strchr(value, ' ');
3849 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3852 END_HASH_ITERATION(hash, itr)
3854 freeSetupFileHash(level_setup_hash);
3858 Error(ERR_DEBUG, "using default setup values");
3864 void SaveLevelSetup_SeriesInfo()
3867 char *level_subdir = leveldir_current->subdir;
3868 char *level_nr_str = int2str(level_nr, 0);
3869 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3873 /* ----------------------------------------------------------------------- */
3874 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3875 /* ----------------------------------------------------------------------- */
3877 InitLevelSetupDirectory(level_subdir);
3879 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3881 if (!(file = fopen(filename, MODE_WRITE)))
3883 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3888 fprintFileHeader(file, LEVELSETUP_FILENAME);
3890 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3892 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3893 handicap_level_str));
3895 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3898 if (LevelStats_getPlayed(i) > 0 ||
3899 LevelStats_getSolved(i) > 0)
3904 sprintf(token, "%03d", i);
3905 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3907 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3913 SetFilePermissions(filename, PERMS_PRIVATE);
3918 int LevelStats_getPlayed(int nr)
3920 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3923 int LevelStats_getSolved(int nr)
3925 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3928 void LevelStats_setPlayed(int nr, int value)
3930 if (nr >= 0 && nr < MAX_LEVELS)
3931 level_stats[nr].played = value;
3934 void LevelStats_setSolved(int nr, int value)
3936 if (nr >= 0 && nr < MAX_LEVELS)
3937 level_stats[nr].solved = value;
3940 void LevelStats_incPlayed(int nr)
3942 if (nr >= 0 && nr < MAX_LEVELS)
3943 level_stats[nr].played++;
3946 void LevelStats_incSolved(int nr)
3948 if (nr >= 0 && nr < MAX_LEVELS)
3949 level_stats[nr].solved++;